Sui Bella Ciao — Inside the New Move VM
Sui rewrote their entire Move VM. Here's how the new execution layer works, what changed from the old one, and where the interesting attack surface lives for security researchers.
What Is Bella Ciao?
Bella Ciao is the internal codename for Sui's complete rewrite of the Move VM — the engine that executes every Move smart contract on the Sui network. It shipped as execution version 4, enabled at protocol version 118, behind a feature flag called new_vm_enabled.
This isn't a patch or an optimization pass. It's a new VM with a different architecture, new caching layer, new package resolution, and a new programmable transaction block (PTB) execution pipeline.
The 4-Layer Architecture
The new VM is structured as four layers, each with a clear responsibility:
Layer 1: The Router
A single file (sui-execution/src/lib.rs) that reads the protocol config and dispatches to the correct VM version. This is auto-generated code with a simple match statement:
| Execution Version | Module | VM Type | Protocol Versions |
|---|---|---|---|
| 0 | v0 | MoveVM (old) | < 18 |
| 1 | v1 | MoveVM (old) | 18 — 30 |
| 2 | v2 | MoveVM (old) | 31 — 37 |
| 3 | v3 | MoveVM (old) | 38 — 117 |
| 4 | latest | MoveRuntime (new) | >= 118 |
Old versions (v0–v3) are kept frozen as snapshots so nodes syncing historical state produce identical results. This is how you ship a VM rewrite on a live network without breaking consensus.
There's also an encapsulation test that verifies sui-node and sui-replay can only access execution layer crates through sui-execution. If any crate bypasses this routing, the test fails — because bypassing could mean the wrong VM version gets used, causing a consensus fork.
Layer 2: The Adapter
The adapter (sui-execution/latest/sui-adapter/) is the bridge between Sui's transaction model and the Move VM. It has several key components:
execution_engine.rs — the main orchestrator. Takes a Sui transaction, sets up the execution environment, runs it, settles gas, and produces TransactionEffects.
static_programmable_transactions/ — the new PTB execution pipeline. This is where bella ciao fundamentally differs from the old VM. PTBs now go through a 4-stage pipeline:
The SPT module is hardened with #![deny(clippy::arithmetic_side_effects)] and #![deny(clippy::indexing_slicing)] — compile-time guards against overflow and out-of-bounds panics.
execution_mode.rs — defines 4 execution modes that control what's allowed:
| Mode | Arbitrary Calls | Arbitrary Values | Skip Conservation | Used For |
|---|---|---|---|---|
| Normal | No | No | No | User transactions |
| Genesis | Yes | Yes | No | Chain bootstrap |
| System | Yes | Yes | No | Epoch changes |
| DevInspect | If SKIP | If SKIP | If SKIP | Dry-run simulation |
Normal is the only mode users can trigger. The others bypass visibility and type constraints for system operations.
temporary_store.rs — the transactional object store (57KB, the largest file). Tracks every object create, mutate, and delete during execution.
gas_charger.rs / gas_meter.rs — manages gas payment (coins vs address-balance) and per-instruction gas metering.
Layer 3: The Natives
When Move code calls a native function like sui::transfer::transfer(), it crosses into Rust. The sui-move-natives/ crate implements all of Sui's native functions:
Object lifecycle — object.rs handles new_id, delete_id, borrow_uid. These are the primitives for creating and destroying object identity.
Transfer operations — transfer.rs implements transfer_internal, freeze_object, share_object, receive_object_internal, and the new party_transfer_internal.
Dynamic fields — dynamic_field.rs handles child object CRUD (add, borrow, remove, has).
Crypto — 14 files covering ed25519, ecdsa (k1/r1), bls12381, groth16 (ZK-SNARKs), poseidon, zklogin, VDF, and nitro attestation.
The ObjectRuntime (object_runtime/mod.rs) is the most critical piece. It's the in-memory ledger that tracks all state changes during a transaction — new IDs, deleted IDs, transfers, events, dynamic field mutations. At the end of execution, it produces RuntimeResults which become the transaction's effects.
Layer 4: The Move VM
This is the core of bella ciao — the actual bytecode execution engine in external-crates/move/crates/.
The biggest architectural change is the split between MoveRuntime and MoveVM:
- single long-lived instance
- per-module loading
- direct execution
struct Executor(Arc<MoveVM>)
- MoveRuntime: long-lived cache
- MoveVM: ephemeral per-execution
- package-level VTable caching
struct Executor(Arc<MoveRuntime>)
MoveRuntime is created once per epoch. It holds a MoveCache (for loaded packages), NativeFunctions, and VMConfig. When a transaction needs to execute Move code, it calls make_vm() which creates a short-lived MoveVM instance bound to a specific LinkageContext (the set of exact package versions to use).
The MoveVM instance gets VMDispatchTables — pre-computed virtual function dispatch tables for all packages in the linkage context. These tables are cached by LinkageHash, so the same dependency graph doesn't need to be rebuilt for every transaction.
The Verification Pipeline
When someone publishes a new Move package, it goes through two verification layers:
Move bytecode verifier — the standard Move verifier checks type safety, reference safety, resource safety, etc. Runs with a meter that enforces a time budget per module. If verification times out, the publish is rejected.
Sui-specific verifier (sui-verifier/) — runs 7 additional passes:
struct_with_key_verifier— structs withkeymust haveid: UIDas first fieldglobal_storage_access_verifier— blocksmove_to,move_from,borrow_global(Sui uses object model)id_leak_verifier— prevents leaking/unpackingUIDprivate_generics_verifier— certain generic types can only be instantiated by their defining moduleentry_points_verifier— validatesinit()andentryfunction signaturesone_time_witness_verifier— validates the OTW pattern
What Changed — Old vs New
| Aspect | Old (v3) | New (bella ciao) |
|---|---|---|
| VM core | MoveVM (monolithic) | MoveRuntime + ephemeral MoveVM |
| Package loading | Per-module | Package-level with MoveCache |
| PTB execution | Direct | 4-stage: linkage → load → type → execute |
| Dispatch | Direct resolution | VTable caching by LinkageHash |
| Arithmetic | Standard | checked_arithmetic macro + clippy deny |
| New features | — | party_transfer, accumulators, settlement |
Where the Attack Surface Lives
For security researchers looking at the bella ciao bounty, here are the areas worth focusing on:
VTable cache keying. Dispatch tables are cached by LinkageHash. If two different linkage contexts produce the same hash, a cached VTable from one context could be reused for another. There's a sanity check that logs an error and reloads — but it recovers rather than hard-failing.
debug_assert-only invariants. Critical state invariants in ObjectRuntime::finish() — like transfer/delete exclusivity and child object ownership consistency — are enforced via debug_assert!. In release builds, these are no-ops. If an invariant is violated, execution proceeds silently.
Verifier timeout classification. The function check_for_verifier_timeout() treats REFERENCE_SAFETY_INCONSISTENT as a timeout error. This could also be a real reference safety violation — it gets logged as a debug_fatal! but is still classified as a timeout, not a hard verification failure.
The v3-to-v4 behavioral delta. Same trait interface, different implementations. Any semantic divergence in how the old and new VM execute the same transaction is potentially exploitable. The interesting diff lives between external-crates/move/move-execution/v3/ (old) and external-crates/move/crates/ (new).
The SPT pipeline. The static_programmable_transactions/ module is entirely new code. Its own AST, typing system, linkage resolution, and interpreter. Fresh code means fresh bugs.
party_transfer_internal. A new native function that currently restricts to exactly 1 party member with ALL permissions. The permission bitmask (READ=1, WRITE=2, DELETE=4, TRANSFER=8) suggests future multi-party ownership. Any bypass of the single-party restriction before the feature is fully ready could be dangerous.
Scope: New Move VM — execution version 4, protocol version 118+
Key Directories: sui-execution/latest/, external-crates/move/crates/
Feature Flag: new_vm_enabled
Follow: @thepantherplus