Ta strona opisuje, jak umieścić program w łańcuchu i go wywołać: czym jest program, jak go zbudować, co wywołanie ujawnia, a co ukrywa, jak odczytać potwierdzenie i na jakie limity natrafisz. Każdy program to kod publiczny, a każde wywołanie to dowód. Przykładowe programy zawierają kompletne aplikacje, a randprotocol/example-programs zawiera kod źródłowy programów wdrożonych w chain 20, w tym tych zgodnych z RPL-2 (poniżej).
Czym jest program
Program to kod maszynowy RV32IM dla Rand zkVM: { base_pc: u32, words: Vec<u32> }, co najwyżej tyle słów, ile wynosi limit programu w danym łańcuchu (65 535 słów w chain 20, jak w każdym łańcuchu od chain 13; 4096 w genesis bez max_program_words, takim jak chain 12), przy czym base_pc jest wielokrotnością 4, a każde słowo dekoduje się jako instrukcja (słowo, które się nie dekoduje, jest odrzucane przy wdrożeniu, nigdy w trakcie wykonania). Chain 20, podobnie jak chain 19, odmawia też wdrożenia programu, którego żadne wywołanie nie zdołałoby pomieścić: ponad 8180 słów bez segmentu publicznego, mniej niż tyle z segmentem (poniżej). Program jest adresowany zawartością i niezmienny:
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
Wywołanie to dowód STARK, że program o skrócie hc został wykonany na pewnych prywatnych danych wejściowych, zużył nie więcej niż zadeklarowany limit gazu i opublikował osiem słów wyjścia. Węzeł weryfikuje dowód, zapisuje potwierdzenie i nic więcej się nie dzieje: wyjścia są danymi, nie skutkami. Wywołanie nie może przenieść RAND; wartość przenosi wywołujący w pakiecie, który płaci za wywołanie, a wyjścia programu znaczą to, co program i jego wywołujący uznają, że znaczą. W chain 20 wyjątkiem jest Invoke: jego dowód poświadcza zadeklarowane przejście stanu, które księga następnie stosuje (Stan programu (RPL-2)).
Co może robić program gościa:
| grupa | instrukcje |
|---|---|
| górne / skoki | LUI AUIPC JAL JALR |
| rozgałęzienia | BEQ BNE BLT BGE BLTU BGEU |
| odczyty / zapisy | LW SW LB LH LBU LHU SB SH |
| ALU | ADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND |
| RV32M | MUL MULH MULHU MULHSU DIV DIVU REM REMU |
| systemowe | ECALL; nigdy FENCE, CSR, EBREAK ani rozszerzenia A i C |
Wywołania systemowe, według numeru w a7:
| # | wywołanie systemowe | skutek |
|---|---|---|
| 0 | HALT | kończy wykonanie |
| 1 | WRITE_OUTPUT slot word | ustala publiczne wyjście slot < 8; każdy slot co najwyżej raz, niezapisane sloty mają wartość zero |
| 2 | READ_INPUT idx | zwraca prywatne słowo wejścia idx, związane solonym zobowiązaniem H_IN |
| 3 | POSEIDON2 ptr n | haszuje w miejscu n ≤ 4096 słów gąbką Poseidon2 |
| 4 | KECCAK ptr | jedna permutacja Keccak-f[1600] w miejscu; sprawia, że dowód zawiera tablicę keccak |
| 5 | SHA256 ptr | jedna kompresja SHA-256 w miejscu; sprawia, że dowód zawiera tablicę sha256 |
| 6 | READ_PUBLIC idx | zwraca słowo idx segmentu publicznego; w chain 20 segment publiczny jest ustalany przy wdrożeniu (rand program deploy --public); w genesis bez max_program_public_words jest pusty i nic nie odczytuje |
| 7 | POSEIDON2_LEN ptr n | POSEIDON2 z długością wiadomości związaną w gąbce, tak że [a] i [a, 0] mają różne hasze (zestaw ograniczeń 7, chain 16 i późniejsze). rand-guest check w wersji circuits aeacf31 nadal go odrzuca (następna sekcja) |
Pamięć: 32 rejestry, w których x0 ma wartość zero; RAM zaczynający się od zera; każdy adres poniżej 2³⁰; 64 KiB stosu dla skompilowanych programów gościa. Program zbudowany za pomocą rand-guest niesie swoje dane tylko do odczytu i zainicjowane w kontenerze obrazu oraz prolog ładujący, który jest częścią programu i zapisuje te dane, zanim zacznie działać właściwy kod programu (Zestaw instrukcji RISC-V). Zobowiązania do not, unieważniacze i sprawdzenia drzewa Merkle’a nie są wywołaniami systemowymi, lecz procedurami bibliotecznymi opartymi na POSEIDON2.
Od chain 18 (zestaw ograniczeń 8) każde wykonanie jest mierzone w gazie. Zwykła instrukcja oraz wywołania systemowe 0, 1, 2 i 6 kosztują po 1 gaz, POSEIDON2 na n słowach kosztuje 3 + 3 × ⌈n / 4⌉, jedna permutacja KECCAK kosztuje 192, a jedna kompresja SHA256 kosztuje 64. Przykładowe programy zawierają tabelę oraz to, co deklaruje wywołanie.
Budowanie programu
Trzy sposoby, wszystkie dają słowa RV32IM:
Wbudowany asembler. Portfel dostarcza pięć ręcznie asemblowanych programów gościa i asembluje je do pliku 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
Programy kompilowane, za pomocą rand-guest. Narzędzie rand-guest z repozytorium circuits buduje crate Rust no_std (riscv32im-unknown-none-elf, Rust 1.98.1) lub katalog C (clang 23.1.1) do kontenera obrazu, sprawdza każde słowo względem dekodera maszyny i wypisuje hc oraz identyfikator programu. Budowanie jest hermetyczne, więc ten sam kod źródłowy daje to samo hc na każdej maszynie. Dla chain 18 do 20 zbuduj toolchain z commitu circuits aeacf31 (gałąź feat/v067-circuits), czyli commitu przypiętego przez fullnode v0.6.7 i v0.6.8. W tym commicie rand-guest check wymienia tylko wywołania systemowe od 0 do 6, więc program gościa wywołujący guest_sdk::poseidon2_len (wywołanie systemowe 7) jest odrzucany przy budowaniu, choć maszyna go wykonuje (zkp-circuits issue #1). Krok po kroku: Wdrożenie programu w Rust i Wdrożenie programu RISC-V.
Programy tłumaczone. sbpf2rv tłumaczy plik ELF sBPF z Solana, a evm2rv tłumaczy bajtkod uruchomieniowy EVM na programy, których wyjścia słowo w słowo równają się wyjściom programów gościa będących interpreterami Rand: Transpiler Solana i Transpiler Ethereum.
Sam portfel nie ma kompilatora. rand program deploy przyjmuje plik .json, surowy plik .bin ze słowami w porządku little-endian albo kontener obrazu zapisywany przez rand-guest (fullnode v0.3 i nowsze).
Wdrażanie
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
Testnet to chain 20, a wdrażanie, wywoływanie i Invoke w nim wymagają portfela rand z fullnode v0.6.8 lub nowszego. Starszy portfel w ogóle nie może tam wysyłać: genesis chain 20 ustawia binding_domain: 1, więc każde powiązanie dowodu i każda podpisana wiadomość zawiera hasz genesis, a powiązania portfela v0.6.7 niosą sam identyfikator łańcucha. Publiczny punkt końcowy https://rpc.randprotocol.org obsługuje synchronizację portfela, wdrożenia i wywołania (konfiguracja).
Portfel najpierw wypisuje identyfikator programu, liczbę słów i hc, po czym pyta węzeł o opłatę przez rand_estimateFee, który odrzuca program przekraczający limit łańcucha, zanim cokolwiek zostanie udowodnione. Następnie buduje pakiet samoprzelewu o zerowej wartości (jedyny pakiet ukrytych aktywów 4-wejścia-4-wyjścia, który niesie każda transakcja; opłata jest pobierana z jego slotów RAND), płacący 0.001 RAND + 100,000 units × words z własnych not (słowa segmentu publicznego liczą się jako słowa kodu), dołącza Action::Deploy { base_pc, words, public }, dowodzi pakiet względem powiązania transakcji i wysyła. Od chain 17 są to dwa dowody, a portfel wypisuje po linii dla każdego: autoryzację wydatku tier-10 (sekundy, około 1,37 MB) oraz pakiet tier-14 (około dwóch minut na laptopie, około 1,5 MB). Przed chain 17 był to sam dowód pakietu, około 100 s i około 1,42 MB w chain 15. Opłata za wdrożenie nie ma składnika gazu ani bajtów: gaz wycenia wyłącznie wywołania. Węzeł sprawdza, czy każde słowo się dekoduje, oblicza hc, zapisuje rekord i w tle zaczyna budować klucz weryfikatora. Kod jest publiczny; to, kto za niego zapłacił, już nie — rand_getProgram nie ma pola deployer. Wdrożenie tych samych słów dwukrotnie jest idempotentne.
→ {"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": [ … ] }
Wywoływanie
rand call <program id> --input 400 --input 250 --input 300 --input 75
rand receipt <tx hash>
Portfel pobiera z węzła kod i segment publiczny i sprawdza, czy haszują się do identyfikatora. Uruchamia wywołanie w emulatorze i wypisuje gaz oraz opłatę, zanim cokolwiek udowodni:
gas: 5636 (declaring 6144, tier 12)
fee 0.006191437 RAND (incl. two price steps of headroom)
Następnie pieczętuje transkrypt wejścia (poniżej), dowodzi wywołanie lokalnie w profilu FRI łańcucha, z danymi wejściowymi jako wektorem prywatnym, dowodzi pakiet płacący opłatę, wysyła, czeka i wypisuje potwierdzenie. W chain 20 dowód wywołania jest związany z własną transakcją (powiązaniem zawierającym hasz genesis), a pakiet to dwa dowody, więc wywołanie to trzy dowody: wywołanie, autoryzacja wydatku tier-10 i pakiet tier-14. Dwie powyższe linie pochodzą z wywołania anonimowego głosowania w chain 18 z 2026-09-30 (Anonimowe głosowanie).
Opłata. W chain 20 dolna granica wynosi BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉, gdzie BUNDLE_BASE to 0,001 RAND, bajty to dowód wywołania wraz z kopertą wejść, a ceny zaczynały od 100 i 800 jednostek (jednostka to 10⁻⁹ RAND) i zmieniają się wraz z zapełnieniem bloków. Chain 20 ogranicza je do 10 000 i 80 000 jednostek, a do jego obciążenia wliczają się tylko bajty, które płacą cenę za bajt (dowody wywołań i koperty) (byte_load: "paying"); w chain 18 i 19 ceny nie miały górnej granicy, a bajty każdej transakcji przesuwały cenę za bajt. Płaski harmonogram księgi 0.001 RAND + call_fee(tier), obowiązujący w chain 15, tam nie obowiązuje. Portfel ustala opłatę, zanim powstanie dowód, więc wycenia dowód według limitu 4 MiB dla tego chain i dolicza zapas w postaci dwóch kroków ceny. Cztery zmierzone wywołania w chain 18 kosztowały od 0,0057 do 0,0075 RAND każde (rachunek). rand fee call <tier> --gas <limit> --bytes <B> pyta węzeł o samą dolną granicę. private_payment odczytuje cztery salda prywatne i, jeśli ich suma wynosi co najmniej jego próg, publikuje nadwyżkę: wyjścia [1, 0, 25, 0, 0, 0, 0, 0]. Łańcuch widzi dowód, poziom i te osiem słów; nigdy nie widzi sald.
Flagi: --tier T wymusza poziom; --gas-limit <N|max> deklaruje dokładny limit gazu lub górną granicę poziomu zamiast domyślnej wartości, czyli dokładnego gazu zaokrąglonego w górę do wielokrotności 2^(tier − 2); --fee <RAND> ustawia opłatę; --expect-public <FILE> odmawia przed wygenerowaniem dowodu, chyba że segment publiczny programu składa się ze słów z tego pliku; --auditor <rand1…> zapieczętowuje zapis również dla wskazanego audytora; --print-call-key wypisuje klucz wywołania; --no-envelope nie publikuje zapisu; --cuda wybiera backend GPU (nieprzetestowany na sprzęcie i niezgodny z pieczętowaniem koperty). Chain 20 odrzuca --cuda przy wywołaniu bez wyjątku: dowód wywołania powiązany ze swoją transakcją powstaje na backendzie CPU.
Pokwitowania
Pokwitowanie jest przechowywane dla każdego zatwierdzonego wywołania i przeliczane, gdy węzeł synchronizuje lub weryfikuje swój łańcuch:
→ {"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 to solone zobowiązanie dowodu do prywatnych wejść; samo w sobie niczego nie ujawnia. h_pub to skrót segmentu publicznego programu, ustalonego przy wdrożeniu, czyli public_digest programu, lub null dla programu wdrożonego bez niego. Nie ma pola effect, a zadeklarowany limit gazu nie znajduje się w pokwitowaniu. Eksplorator pokazuje ten sam obiekt jako receipt w GET /transactions/:hash dla call, wypisuje wywołania programu w GET /programs/:id i filtruje GET /transactions?kind=call&program=<id>.
Koperty wejść: ujawnienie wywołania później
Domyślnie rand call publikuje zapis (salt, inputs) wywołania zapieczętowany w kopercie wejść wywołania, aby wejścia można było pokazać później bez drugiego dowodu:
| część | bajty | czym jest |
|---|---|---|
body | 12 + 16 + 4·n + 16 | salt ‖ inputs pod świeżym kluczem wywołania K_call |
to_sender | 60 | K_call opakowany kluczem podglądu wychodzącego wywołującego |
kem_ct | 1,088 lub 0 | enkapsulacja ML-KEM-768 dla audytora, pusta bez audytora |
to_auditor | 60 lub 0 | K_call opakowany pod sekretem tej enkapsulacji |
Dane powiązane treści to H_IN, więc koperta przeniesiona na inne wywołanie nie otworzy się dla nikogo. Łańcuch sprawdza tylko rozmiar (co najwyżej max_call_envelope_bytes: 65 536 bajtów w chain 20, 18 432 w genezie bez tego pola) i zwraca ją bez zmian przez rand_getCallEnvelope(tx). Otwierają ją trzy klucze i żaden więcej: klucz podglądu wywołującego (każde wywołanie wykonane przez ten portfel), klucz wywołania (to jedno wywołanie), klucz podglądu audytora (to jedno wywołanie).
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 otwiera zapis, sprawdza, że input_digest(salt, inputs) == H_IN, ponownie uruchamia program na odzyskanych wejściach w emulatorze, porównuje wyjścia z wyjściami z pokwitowania — i kończy się kodem niezerowym, jeśli któraś z kontroli zawiedzie, aby skrypt nie wziął ostrzeżenia za potwierdzenie. --no-envelope jest nieodwracalne: gdy sól zniknie, nikt, łącznie z wywołującym, nie może otworzyć tego wywołania.
Od v0.6.8 ponowne uruchomienie odbywa się na segmencie publicznym, na którym powstał dowód: słowach publicznych programu ustalonych przy wdrożeniu i, przy hardening_v6, następujących po nich ośmiu słowach powiązania wywołania (zgłoszenie #116 w fullnode). W v0.6.7 open-call działało tylko dla programu wdrożonego bez segmentu publicznego; dla programu z segmentem wypisywało wejścia, po czym zatrzymywało się z komunikatem re-running the program on these inputs failed: PublicIndex(0), kończąc się kodem niezerowym bez wykonania którejkolwiek kontroli.
Stan programu (RPL-2)
Chain 20 jest pierwszym chain z RPL-2, włączonym przez sekcję program_state w jego genezie. Program otrzymuje trzy rzeczy, których wcześniej nie miał (fullnode docs/program-state.md, crates/randprotocol-core/src/ledger/program_state.rs w v0.6.8):
| komórki | mapa z 8-słowowego klucza na 8-słowową wartość, osobna dla każdego programu. Nieobecna komórka daje odczyt ośmiu zer; zapis ośmiu zer ją usuwa. Zatwierdzone w korzeniu stanu |
| skarbiec | publiczne saldo dla każdego aktywa (0 to RAND). Wartość wchodzi przez pakiet invoke, a wychodzi jako noty obliczane przez łańcuch |
| własny token | token zarejestrowany przez rand token create --program <id> (uprawnienie do emisji Program(id), bez podaży początkowej) jest emitowany i spalany tylko przez wywołania invoke tego programu |
Komórki i salda skarbca są publiczne, tak jak podaż tokena. Żadne z nich nie jest notą.
Invoke (Action 33) to wywołanie, które dodatkowo deklaruje przejście, a jego dowód wywołania pokazuje, że program akceptuje dokładnie to przejście:
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
To, co wchodzi, to to, co pakiet spala: burn_r to RAND wpłacony do skarbca; burn_a jednostek burn_asset to token wpłacony (napływ deposit) lub zniszczony (napływ burn, tylko własny token programu). To, co wychodzi, to jedna obliczona przez łańcuch nota na każdą wypłatę, najpierw wypłaty, potem emisje, z kwotą i pk odbiorcy jawnymi, tak jak w przypadku emisji. Księga stosuje przejście tylko wtedy, gdy program istnieje, kształt jest dozwolony, każda komórka w reads nadal ma zadeklarowaną wartość, skarbiec (po wpłacie z tej samej transakcji) pokrywa każdą wypłatę, każda emisja dotyczy własnego tokena programu, a dowody się weryfikują. Odczyt, który już się nie zgadza, jest odrzucany jako StaleRead; portfel odczytuje ponownie i generuje dowód od nowa.
Czym invoke różni się od call.
Call (RPL-1) | Invoke (RPL-2) | |
|---|---|---|
| segment publiczny, na którym powstaje dowód | public ‖ call_binding | public ‖ call_binding ‖ context, przejście jako słowa |
| co robi łańcuch po sukcesie | zapisuje pokwitowanie; wyjścia są danymi | zapisuje pokwitowanie i stosuje przejście: zapisuje komórki, przenosi wartość w skarbcu, dopisuje noty wypłat |
| wartość | tylko własny transfer pakietu | wpłaty do skarbca przez spalenia pakietu; wypłaty i emisje z niego |
| zależność od stanu | nie | dowód nie zależy (segment jest budowany z transakcji), ale kontrola odczytu tak, przy przyjęciu i w puli |
| opłata | dolna granica wywołania | dolna granica wywołania plus cell_fee za każdą utworzoną komórkę |
Program odczytuje słowo kontekstu i przez READ_PUBLIC(public_len + 8 + i). Słowo 0 to wersja (1), słowa od 1 do 4 to liczby odczytów, zapisów, wypłat i emisji, następnie burn_r, rodzaj napływu, burn_asset i burn_a, potem każdy odczyt i zapis jako klucz i wartość (po 8 słów), potem każda wypłata jako aktywo i kwota. Odbiorców nie ma w kontekście: program decyduje o kwotach, a powiązanie ustala, komu się płaci. Program musi ograniczać każde słowo kontekstu, które mu pokazano, i nie może mieć ścieżki kończącej się paniką, ponieważ procedura obsługi paniki w guest-sdk zatrzymuje wykonanie, a zatrzymane wykonanie da się udowodnić.
Limity chain 20 (rand_getLimits.program_state):
| limit | chain 20 |
|---|---|
cell_fee | 10 000 000 jednostek, 0,01 RAND, za każdą utworzoną komórkę (komórkę zapisaną wartością niezerową tam, gdzie nic nie było zapisane); ponowny zapis lub usunięcie komórki jest darmowe |
max_reads | 8 komórek |
max_writes | 8 komórek |
max_payouts | 4 noty, wypłaty i emisje łącznie |
Reguła segmentu jest zwykle ostrzejsza: cały segment musi się zmieścić w tablicy publicznej, na której dowodzi się wywołanie tego samego programu, czyli w 127 słowach dla programu bez wejścia publicznego. Zostaje więc 119 słów kontekstu, na przykład trzy odczyty, trzy zapisy i cztery wypłaty.
Polecenia i RPC (portfel 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 sprawdza każdy wpis reads względem aktywnej komórki przed wygenerowaniem dowodu i kończy się kodem 3 przy nieaktualnym odczycie, przed dowodzeniem lub przy włączeniu do bloku, więc skrypt odczytuje ponownie i ponawia próbę. rand_estimateFee z "kind": "invoke" wycenia opłatę. Format pliku przejścia opisano w docs/cli.md fullnode. Kompletne programy RPL-2 wdrożone w chain 20 (amm, stableswap, orderbook, stablecoin, lending, perp, crowdfund, stoploss) oraz programy RPL-1 (sealed-auction, ballot, eligibility, private-join, credit-score) znajdują się w randprotocol/example-programs.
Co jest jawne, a co ukryte
| publiczne | ukryte |
|---|---|
identyfikator programu i jego kod; poziom (tier); zadeklarowany limit gazu; osiem wyjść; H_IN; opłata pakietu i jego pola publiczne; rozmiar koperty; to, czy wywołanie użyło Keccak czy SHA-256, i mniej więcej jak często (nagłówek dowodu deklaruje wysokość każdej tablicy skrótów) | prywatne dane wejściowe; rejestry, pamięć, rozgałęzienia; rzeczywista liczba cykli i dokładna ilość gazu; sól H_IN; kto wywołał program i czym zapłacił |
Dwa dowody tego samego przebiegu to różne bajty. Na chain 15 i wcześniejszych to nie wystarczało: wywołanie z 31 lub mniejszą liczbą prywatnych słów dowodziło tablicy zbyt małej, by cokolwiek ukryć, i jego dane wejściowe dawały się odzyskać z dowodu. Od v0.6 prover ustawia dla tych tablic dolny próg 128 wierszy, a zestaw ograniczeń 7 (chain 16 i późniejsze) zaślepia sumy dla poszczególnych tablic, które nadal przeciekały. hc jest wiążące, ale nie ukrywające: każdy, kto potrafi wyliczyć programy-kandydaty, może potwierdzić, który z nich został wdrożony.
Czego jeszcze nie da się zrobić
W brzmieniu, w jakim podaje to fullnode:
- Brak wywołań między programami i brak stanu poza RPL-2.
Callto jeden dowód z jednego przebiegu; z wywołania do wywołania nie przechodzi nic poza tym, co wywołujący sam poda ponownie. Na chain 20Invokeodczytuje i zapisuje własne komórki i skarbiec programu (powyżej); żaden chain przed 20 nie ma stanu programu, a invoke nie może sięgnąć do komórek innego programu. - Brak transferów inicjowanych przez program poza skarbcem. Efekt rodzaju 1, transfer opłacany przez program na konto, został usunięty razem z kontami, a wyjścia
Callsą oświadczeniami. Na chain 20Invokewypłaca z własnego skarbca programu, najwyżej cztery noty, a kwoty i odbiorcy są jawni. - Brak kompilatora w portfelu. Programy to wbudowane, wcześniej złożone gościnne obrazy albo obrazy zbudowane za pomocą
rand-guestw repozytorium circuits. - Wejście publiczne jest ustalane przy wdrożeniu, a nie przy każdym wywołaniu. Zestaw ograniczeń 6 dodał
READ_PUBLICiH_PUB. Na chain 20 wdrożenie może zawierać do 32 768 słów publicznych, identyfikator programu je wiąże, a dowód każdego wywołania jest sprawdzany względem ich skrótu. Nie ma słów publicznych przypisanych do pojedynczego wywołania. - Syscalle skrótów dają duże dowody. Dowód zawierający keccak ma około 3,2 MB, czyli wprost przekracza domyślny limit dowodu 2 MiB; dowód zawierający sha256 dodaje około 400 KB. Limit chain 20 wynosi 4 194 304 bajtów (4 MiB, tak jak na chain 17–19; na chain 13–16 było to 8 MiB), co mieści oba przypadki: na chain 18 wywołanie tier 12 z tablicą Keccak miało 3,35 MB, a wywołanie tier 14 z tablicą SHA-256 miało 1,91 MB (Przykładowe programy). Wywołanie ma też limit 128 permutacji Keccak-f i 128 kompresji SHA-256 (poniżej), a każdy KiB dowodu jest rozliczany według ceny za bajt.
- Programy EVM i Solana nie wdrażają się na chain 20, a ich wywołania przekraczają limit poziomu wywołania. Interpretery gościnne (
evm.bin, 18 009 słów;sbpf.bin, 8 317) i przetłumaczone obrazy (ERC-20, 11 686 słów; SPL Token, 65 096) przekraczają domyślny limit 4096 słów i mieszczą się w limicie 65 535 ustawionym przez chain 13 i utrzymywanym przez każdy kolejny. Oba przetłumaczone obrazy wdrożono na chain 13 dnia 2026-09-19, SPL Token z jego ELF jako wejściem publicznym. Chain 14 i 15 uruchomiono od zera i żaden z obrazów nie został na nich wdrożony ponownie. Chain 20, podobnie jak chain 19, odmawia wdrożenia któregokolwiek z tych czterech: zgodnie z regułąhardening_v6program, którego nie pomieściłoby żadne wywołanie tier 14, czyli mający więcej niż 8 180 słów bez segmentu publicznego, a z nim mniej, jest odrzucany przy wdrożeniu (ProgramUncallable), aby nie płacić opłaty za wdrożenie programu, którego nikt nie może wywołać. Wywołanie ERC-20 dowodzi się na tier 16 lub 18:approvena tier 16 wywołano na chain 13 dnia 2026-09-19 z produkcyjnym dowodem o rozmiarze 3 412 405 bajtów (Transpiler Ethereum), a chain 20 odrzuca każde wywołanie powyżej tier 14 (następny punkt), więc dziś żadne wywołanie ERC-20 nie jest tam dopuszczalne. Wywołania SPL Token to tier 20, który wymaga około 330 GB; nie istnieje żaden dowód wywołania SPL (pamięć dowodzenia). Opkody kontekstu bloku EVM kończą się pułapką, a stanu EVM w łańcuchu nie ma. - Wywołanie dowodzi się na tier 14 lub niższym. Od fullnode v0.5.6 (2026-09-25) dowód wywołania powyżej tier 14 jest odrzucany (
CallTierTooHigh), podobnie jak dowód z więcej niż 128 permutacjami Keccak-f lub 128 kompresjami SHA-256.approveERC-20 na tier 16 z chain 13 zostałby na chain 20 odrzucony, tak jak zostałby odrzucony na chain 15, 18 i 19, i tak samo każde wywołanie ERC-20 na tier 18 lub SPL Token na tier 20; takie wywołania nadal da się udowodnić poza łańcuchem (budżet). - Dowodzenie na GPU nigdy nie zostało uruchomione na sprzęcie.
--cudawymaga kompilacji z--features cudai zatwierdzonego PTX; żadne z nich nie istnieje we flocie. Na chain 20 dowód wywołania powstaje na backendzie CPU niezależnie od kompilacji. hcnie jest ukrywające, jak wyżej, a żądanie świadka nadal informuje węzeł, który liść wydaje portfel.