VM(Py) Sandbox — Forbidding Nondeterminism & Allowed Libraries
What the VM forbids to guarantee determinism (I/O, clocks, floats, ambient randomness) and which stdlib modules contracts may import.
5 min read 1,009 words
View docs/vm/SANDBOX.md on GitHub
Source: docs/vm/SANDBOX.md — this page mirrors the repository documentation.
Status: Stable (v1)
Audience: Contract authors, reviewers, VM/toolchain maintainers
Source of truth: vm_py/validate.py, vm_py/runtime/*, vm_py/stdlib/*, docs/vm/DETERMINISM.md
This document defines the determinism contract for Python-based smart contracts and lists what is allowed vs forbidden inside the VM sandbox.
1) Goals
- Bit-for-bit determinism across nodes, platforms, and time.
- Auditability: small, explicit allowlist; everything else is rejected.
- Resource safety: bounded memory/CPU, predictable gas, and size caps.
2) High-level Rules
- No ambient I/O: no filesystem, network, clock, environment, or randomness.
- No concurrency: no threads, async, signals, or shared state beyond the VM’s own storage APIs.
- Purely integer semantics: floats are banned to avoid platform drift.
- Stable hashing only:
sha3_256,sha3_512, andkeccak256via the VM’s hash APIs. - Deterministic PRNG only: available as a sandbox stub seeded from the transaction hash (see §5.3).
- Explicit side-effects: storage, events, and syscalls are explicit instructions and charged gas.
- Bounded loops only: must have statically known or policy-capped bounds (enforced at compile time).
- No reflection or dynamic code:
eval/exec, dynamic imports, and runtime attribute introspection beyond primitives are disallowed.
Violations are rejected at validation time (ValidationError) before IR is produced.
3) Disallowed Python Features (Non-exhaustive)
- Imports: Any
importother than the VM-providedstdlibsurface is rejected. - I/O & OS:
os,sys,pathlib,io,subprocess,shutil,socket,ssl,urllib,http*,ctypes,multiprocessing. - Time/entropy:
time,datetime,random,secrets,uuid,hashlib(use VM hash APIs instead). - Concurrency:
threading,asyncio,signal,queue. - Reflection/dynamic:
inspect,types,eval,exec,compile,globals,locals,vars,getattr/setattr(beyond safe primitives). - Parsing/regex side channels:
re(unbounded worst-case),json(use ABI enc/dec),pickle(unsafe). - Floats/decimals:
float,decimal,fractions(non-portable semantics). - Data science / large libs: anything not in the explicit allowlist.
The validator enforces an AST whitelist and rejects disallowed nodes and names with source spans.
4) Allowed Builtins & Patterns
- Builtins (subset):
len,range(with static/capped bounds),int,bool,bytes,bytearray(bounded), tuple/list/dict construction (bounded),enumerate,min/max(on ints),abs,all/any(bounded). - Control flow:
if/else,foroverrange(K),whileonly when compiled to bounded loop with static guard;break/continuepermitted within bounds. - Data: bytes and integer arithmetic; small tuples/lists/dicts with policy-capped sizes.
- Errors: use
abi.revert(b"message")(not Pythonraise).
If in doubt, the validator rejects; extend the allowlist via governance if needed.
5) VM-Provided Libraries (Allowlist)
These are the only imports permitted:
from stdlib import storage, events, hash, abi, treasury, syscalls, random
5.1 stdlib.storage
- get(key: bytes) -> int|bytes
- set(key: bytes, value: int|bytes) -> None
- delete(key: bytes) -> None Keys/values are size-capped; gas accounts for key length and value length.
5.2 stdlib.events
- emit(name: bytes, args: dict[bytes, int|bytes]) -> None Event name and payload sizes are capped and charged.
5.3 stdlib.random
- randbytes(n: int) -> bytes Deterministic PRNG seeded from the tx hash within a call context; same inputs → same outputs. Upper-bound on n enforced by policy.
5.4 stdlib.hash
- keccak256(b: bytes) -> bytes32
- sha3_256(b: bytes) -> bytes32
- sha3_512(b: bytes) -> bytes64 Stable, pure functions; gas proportional to input size.
5.5 stdlib.abi
- require(cond: bool, msg: bytes=b"") -> None
- revert(msg: bytes) -> None
- encode(..)/decode(..) for declared ABI scalars/tuples (bounded). Use these rather than Python json/struct.
5.6 stdlib.treasury
- balance() -> int
- transfer(to: bytes, amount: int) -> None No floating value types; all int with bounds.
5.7 stdlib.syscalls (capability shims)
- blob_pin(ns: int, data: bytes) -> bytes32
- ai_enqueue(model: bytes, prompt: bytes) -> task_id
- quantum_enqueue(circuit: bytes, shots: int) -> task_id
- zk_verify(circuit_id: bytes, proof: bytes, public_inputs: bytes) -> bool These are deterministic envelopes with length caps. Enqueue calls do not yield variable-latency results inside the same block; results are consumed next block via host resolution (see Capabilities spec). The sandbox enforces that call shapes are deterministic and size-bounded; gas includes a fixed call surface cost plus size components.
6) Numeric & Data Determinism
- Integers only: unlimited precision with policy caps. No floats.
- Byte order: big-endian where applicable, mandated by ABI helpers.
- Hashing: via stdlib.hash only.
- Equality/ordering: defined only for supported types; mixed-type comparisons are rejected at validation.
7) Loop & Memory Caps
- Static bounds or manifest/policy hints must allow an upper bound at compile time.
- Per-call caps: maximum locals/stack slots, bytes allocations, event arg totals, and storage touched entries.
- Estimator uses caps to produce a safe gas upper bound; exceeding dynamic size caps triggers Revert.
8) Deterministic PRNG (Details)
- Source: Tx hash (and call index within the transaction), mixed through a VM-local stream cipher.
- Scope: per-call; reseeded for each call entry.
- Use cases: sampling, shuffling with bounded domain sizes.
- Never used for consensus beacons or security-critical randomness—see randomness/ module for the on-chain beacon.
9) Versioning & Reproducibility
- The runtime exposes vm_version, gas_table_version, and bytecode_hash for receipts.
- Tooling pins encoder and gas tables; changing them requires a version bump and recompile.
10) Examples
✅ Allowed
from stdlib import storage, events, hash, abi
def set_name(b: bytes):
abi.require(len(b) <= 64, b"too long")
storage.set(b"name", b)
events.emit(b"SetName", {b"len": len(b)})
def id32(b: bytes) -> bytes:
return hash.sha3_256(b)
❌ Rejected (nondeterminism)
import time # forbidden import
def now():
return time.time() # nondeterministic
11) Review Checklist
- No disallowed imports or AST nodes.
- Loops have static or policy bounds.
- All bytes/collections sizes enforced via require(…).
- Only stdlib.* calls for side-effects.
- No floats or implicit coercions.
- Gas estimator upper bound generated and reasonable.
12) Extending the Allowlist
Changes to the allowlist or sandboxed surfaces must:
- Demonstrate deterministic semantics and size/gas caps.
- Include tests (validator + runtime) and docs updates.
- Ship behind a feature flag and gated version bump.
See also:
- docs/vm/COMPILER.md — pipeline & static gas estimation
- docs/spec/ENCODING.md — canonical encodings & hashing
- docs/spec/CAPABILITIES.md — syscalls ABI and determinism rules
This page mirrors a file in the animicaorg/all repository. If the repository and this page ever disagree, the repository is authoritative. For long-form explainers written for newcomers, see Learn.