Skip to content

Runtime Specification

Complete reference for the embedded NeoVM runtime used by the Neo Solidity compiler for testing and execution. This documents the execution model, opcode support, syscall implementation, gas accounting, and storage emulation.

NeoVM Execution Model

The Neo Virtual Machine (NeoVM) is a stack-based virtual machine designed for executing smart contracts on the Neo N3 blockchain. The embedded runtime in neo-solc faithfully emulates this execution model for local testing.

Stack Machine Architecture

NeoVM uses two stacks for computation:

  • Evaluation Stack -- the primary operand stack. All instructions consume operands from and push results to this stack. Stack items can be integers (arbitrary precision), byte arrays, booleans, arrays, structs, maps, or interop handles.
  • Alt Stack -- an auxiliary stack for temporary storage. Items can be moved between the evaluation stack and alt stack using TOALTSTACK and FROMALTSTACK.

Slots

NeoVM provides three categories of indexed variable slots, initialized per execution context:

Slot TypeOpcodesPurpose
LocalLDLOC0-LDLOC6, LDLOC, STLOC0-STLOC6, STLOCFunction-local variables
ArgumentLDARG0-LDARG6, LDARG, STARG0-STARG6, STARGFunction parameters
StaticLDSFLD0-LDSFLD6, LDSFLD, STSFLD0-STSFLD6, STSFLDContract-level static storage

Slots are allocated with INITSLOT (locals + arguments) or INITSSLOT (static slots). Each slot holds a single stack item of any type.

Execution Contexts

Each function call creates a new execution context containing:

  • Its own evaluation stack frame
  • Local and argument slots (sized by INITSLOT)
  • An instruction pointer into the script bytecode
  • Exception handling state (try/catch/finally frames)

The CALL instruction pushes a new context; RET pops it and returns control to the caller.

Opcode Support

The embedded runtime implements the full Neo N3 opcode set. Opcodes are organized by category below with their hex codes and gas costs.

Constants

OpcodeHexGasDescription
PUSHINT80x001Push 1-byte signed integer
PUSHINT160x011Push 2-byte signed integer
PUSHINT320x021Push 4-byte signed integer
PUSHINT640x031Push 8-byte signed integer
PUSHINT1280x041Push 16-byte signed integer
PUSHINT2560x051Push 32-byte signed integer
PUSHT0x081Push true
PUSHF0x091Push false
PUSHA0x0A1Push address (pointer)
PUSHNULL0x0B1Push null
PUSHDATA10x0C2Push data (1-byte length prefix)
PUSHDATA20x0D2Push data (2-byte length prefix)
PUSHDATA40x0E2Push data (4-byte length prefix)
PUSHM10x0F1Push -1
PUSH0-PUSH160x10-0x201Push small integers 0 through 16

Flow Control

OpcodeHexGasDescription
NOP0x211No operation
JMP / JMP_L0x22 / 0x232Unconditional jump (short/long offset)
JMPIF / JMPIF_L0x24 / 0x252Jump if top is true
JMPIFNOT / JMPIFNOT_L0x26 / 0x272Jump if top is false
JMPEQ / JMPEQ_L0x28 / 0x292Jump if top two are equal
JMPNE / JMPNE_L0x2A / 0x2B2Jump if top two are not equal
JMPGT / JMPGT_L0x2C / 0x2D2Jump if greater than
JMPGE / JMPGE_L0x2E / 0x2F2Jump if greater or equal
JMPLT / JMPLT_L0x30 / 0x312Jump if less than
JMPLE / JMPLE_L0x32 / 0x332Jump if less or equal
CALL / CALL_L0x34 / 0x35512Call function (short/long offset)
CALLA0x36512Call address on stack
CALLT0x37512Call method token (native contract optimization)
ABORT0x381Abort execution unconditionally
ASSERT0x391Abort if top is false
THROW0x3A1Throw exception
TRY / TRY_L0x3B / 0x3C1Begin try block (catch/finally offsets)
ENDTRY / ENDTRY_L0x3D / 0x3E1End try block
ENDFINALLY0x3F1End finally block (rethrows pending exception)
RET0x400Return from current context
SYSCALL0x4110Invoke system interop (4-byte hash follows)

Stack Operations

OpcodeHexGasDescription
DEPTH0x432Push stack depth
DROP0x452Remove top item
NIP0x462Remove second item
XDROP0x482Remove item at index N
CLEAR0x492Clear the stack
DUP0x4A2Duplicate top item
OVER0x4B2Copy second item to top
PICK0x4D2Copy item at index N to top
TUCK0x4E2Insert top item below second
SWAP0x502Swap top two items
ROT0x512Rotate top three items
ROLL0x522Move item at index N to top
REVERSE30x532Reverse top 3 items
REVERSE40x542Reverse top 4 items
REVERSEN0x552Reverse top N items

Splice and Buffer

OpcodeHexGasDescription
NEWBUFFER0x884Create new buffer of given size
MEMCPY0x894Copy bytes between buffers
CAT0x8B4Concatenate two byte arrays
SUBSTR0x8C4Extract substring
LEFT0x8D4Take left N bytes
RIGHT0x8E4Take right N bytes

Bitwise and Logic

OpcodeHexGasDescription
INVERT0x903Bitwise NOT
AND0x913Bitwise AND
OR0x923Bitwise OR
XOR0x933Bitwise XOR
EQUAL0x973Equality comparison
NOTEQUAL0x983Inequality comparison

Numeric

OpcodeHexGasDescription
SIGN0x993Push sign (-1, 0, 1)
ABS0x9A3Absolute value
NEGATE0x9B3Negate
INC / DEC0x9C / 0x9D3Increment / decrement
ADD / SUB0x9E / 0x9F3Addition / subtraction
MUL / DIV / MOD0xA0 / 0xA1 / 0xA25Multiplication / division / modulo
POW0xA38Exponentiation
SQRT0xA46Integer square root
MODMUL / MODPOW0xA5 / 0xA68Modular multiply / modular power
SHL / SHR0xA8 / 0xA93Shift left / shift right
NOT0xAA2Logical NOT
BOOLAND / BOOLOR0xAB / 0xAC2Logical AND / OR
NZ0xB12Non-zero test
NUMEQUAL / NUMNOTEQUAL0xB3 / 0xB43Numeric equality
LT / LE / GT / GE0xB5-0xB83Comparison operators
MIN / MAX0xB9 / 0xBA3Minimum / maximum
WITHIN0xBB3Range check (a <= x < b)

Compound and Collections

OpcodeHexGasDescription
PACKMAP0xBE4Pack key-value pairs into map
PACKSTRUCT0xBF4Pack items into struct
PACK / UNPACK0xC0 / 0xC14Pack/unpack array
NEWARRAY0 / NEWARRAY / NEWARRAY_T0xC2 / 0xC3 / 0xC44Create arrays
NEWSTRUCT0 / NEWSTRUCT0xC5 / 0xC64Create structs
NEWMAP0xC84Create empty map
SIZE0xCA4Get collection/string size
HASKEY0xCB4Check if key exists
KEYS / VALUES0xCC / 0xCD4Get map keys / values
PICKITEM0xCE4Get item by index/key
APPEND0xCF4Append to array
SETITEM0xD04Set item by index/key
REVERSEITEMS0xD14Reverse collection in place
REMOVE0xD24Remove item by index/key
CLEARITEMS0xD34Clear all items
POPITEM0xD44Pop last item from array

Type Operations

OpcodeHexGasDescription
ISNULL0xD82Check if top is null
ISTYPE0xD92Check if top matches type code
CONVERT0xDB2Convert top to specified type

Supported type codes:

CodeType
0x20Boolean
0x21Integer
0x28ByteString
0x30Buffer
0x40Array
0x41Struct
0x48Map
0x60InteropInterface

Extensions

OpcodeHexGasDescription
ABORTMSG0xE01Abort with message
ASSERTMSG0xE11Assert with message

Syscall Implementation

Syscalls are the bridge between NeoVM bytecode and the Neo N3 platform services. They are invoked via the SYSCALL opcode followed by a 4-byte interop ID.

Syscall ID Computation

Syscall IDs are the first 4 bytes of the SHA-256 hash of the syscall name string:

ID = SHA256("System.Storage.Get")[0..4]

This is computed at compile time and embedded in the bytecode.

Storage Syscalls

SyscallDescription
System.Storage.GetContextGet the current contract's read-write storage context
System.Storage.GetReadOnlyContextGet a read-only storage context
System.Storage.AsReadOnlyConvert a context to read-only
System.Storage.GetRead a value by key from storage
System.Storage.PutWrite a key-value pair to storage
System.Storage.DeleteDelete a key from storage
System.Storage.FindFind entries by key prefix, returns iterator
System.Storage.Local.GetRead from local storage
System.Storage.Local.PutWrite to local storage
System.Storage.Local.DeleteDelete from local storage
System.Storage.Local.FindFind in local storage by prefix

Iterator Syscalls

SyscallDescription
System.Iterator.NextAdvance iterator to next entry; returns boolean
System.Iterator.ValueGet current iterator entry value
System.Iterator.DisposeFree the iterator handle and release resources

Iterator handles are created by Storage.Find and are validated by ISTYPE (type code 0x80) while alive. Disposed handles fail ISTYPE checks.

INFO

Iterator.Dispose returns a boolean and invalidates the handle. After disposal, ISTYPE checks against type code 0x80 will return false.

Runtime Syscalls

SyscallDescription
System.Runtime.GetTriggerGet execution trigger type
System.Runtime.PlatformGet platform identifier string
System.Runtime.GetNetworkGet network magic number
System.Runtime.GetAddressVersionGet address version byte
System.Runtime.GetTimeGet current block timestamp
System.Runtime.GetScriptContainerGet the transaction that triggered execution
System.Runtime.GetExecutingScriptHashGet hash of currently executing contract
System.Runtime.GetCallingScriptHashGet hash of the calling contract
System.Runtime.GetEntryScriptHashGet hash of the entry-point contract
System.Runtime.LoadScriptLoad and execute a script
System.Runtime.CheckWitnessVerify that the specified account has witnessed the transaction
System.Runtime.GetInvocationCounterGet number of times current contract was called
System.Runtime.GetRandomGet a pseudo-random number
System.Runtime.LogEmit a log message
System.Runtime.NotifyEmit a notification event (name + state array)
System.Runtime.GetNotificationsGet notifications emitted so far
System.Runtime.GasLeftGet remaining gas
System.Runtime.BurnGasBurn specified amount of gas
System.Runtime.CurrentSignersGet current transaction signers
System.Runtime.SerializeSerialize a stack item to byte array
System.Runtime.DeserializeDeserialize a byte array back to a stack item

Crypto Syscalls

SyscallDescription
System.Crypto.CheckSigVerify a single signature (secp256k1)
System.Crypto.CheckMultisigVerify multiple signatures

Additional crypto operations are available through the CryptoLib native contract: SHA256, RIPEMD160, Keccak256, Murmur32, Hash160, Hash256.

Contract Syscalls

SyscallDescription
System.Contract.CallCall another contract by hash
System.Contract.GetCallFlagsGet current call flags
System.Contract.CreateStandardAccountCreate standard account script hash from public key
System.Contract.CreateMultisigAccountCreate multisig account script hash

Gas Accounting

The embedded runtime tracks gas consumption to approximate production Neo N3 behavior.

How Gas Works

Every opcode and syscall has an associated gas cost. The runtime deducts gas before executing each instruction. If gas reaches zero, execution halts with an out-of-gas error.

Opcode Gas Costs

Gas costs by opcode category:

CategoryTypical GasNotes
Constants (PUSH*)1-2PUSHDATA variants cost 2
Flow control (JMP*)2CALL variants cost 512
Stack operations2All stack manipulation
Slot operations2-3INITSLOT costs 3
Splice/buffer4All buffer operations
Bitwise/logic2-3
Numeric (basic)3ADD, SUB, comparisons
Numeric (complex)5-8MUL, DIV, POW, MODPOW
Collections4All compound operations
Type operations2ISNULL, ISTYPE, CONVERT
RET0Free

Syscall Gas Costs

Syscall CategoryGas CostNotes
Storage context (GetContext, AsReadOnly)1Cheap metadata operations
Storage read (Get, Find)100Per-operation
Storage write (Put)1,000Most expensive common operation
Storage delete (Delete)100
Iterator (Next, Value)1
Runtime metadata (Platform, GetTime, etc.)1
Runtime.CheckWitness200Signature verification
Runtime.GetRandom50
Runtime.Log / Notify1
Crypto.CheckSig / CheckMultisig1,000Cryptographic verification
Contract.Call10
Contract.Create*Account10

Current Accuracy

Gas accounting is approximately 85% accurate compared to the production Neo N3 reference implementation. Known gaps:

  • Dynamic costs for large integer operations are approximated
  • Some complex operations use fixed costs instead of size-dependent costs
  • Edge cases in exception handling gas effects need spec verification

TIP

For precise gas estimation, deploy to Neo-Express and measure actual consumption. The embedded runtime provides a good approximation for development but should not be used as the sole source of gas budgeting for mainnet.

Storage Emulation

The embedded runtime provides a full in-memory storage emulation layer.

Storage Context Model

Each contract has its own isolated storage context. Storage contexts can be:

  • Read-write -- obtained via System.Storage.GetContext
  • Read-only -- obtained via System.Storage.GetReadOnlyContext or AsReadOnly

Write operations (Put, Delete) on a read-only context will fail.

Get / Put / Delete Semantics

  • Get: Returns the byte array value for a given key, or null if the key does not exist.
  • Put: Writes a key-value pair. Both key and value are byte arrays. Overwrites existing values.
  • Delete: Removes a key-value pair. No error if the key does not exist.

Find and Iterator Operations

Storage.Find takes a key prefix and returns an iterator handle over all matching entries:

  1. The runtime materializes all entries matching the prefix into an ordered list
  2. An iterator token (byte handle) is created and pushed onto the stack
  3. Iterator.Next advances the cursor and returns true if an entry is available
  4. Iterator.Value returns the current entry as a key-value struct
  5. Iterator handles are automatically invalidated when disposed

WARNING

The current implementation materializes all matching entries at Find time. This works correctly but is less efficient than streaming for large datasets. See Parity and Limitations for details.

Event and Notification System

Runtime.Notify Semantics

The System.Runtime.Notify syscall emits a notification event consisting of:

  • Event name: A string identifying the event type
  • State array: An array of stack items containing the event data

In Solidity, emit statements are compiled to Runtime.Notify calls. The event name corresponds to the Solidity event name, and the indexed/non-indexed parameters are packed into the state array.

solidity
// Solidity
emit Transfer(from, to, amount);

// Compiles to NeoVM:
// PUSH3 args onto stack, PACK into array
// PUSHDATA "Transfer"
// SYSCALL System.Runtime.Notify

Runtime.Log

System.Runtime.Log emits a simple string log message. This is used for debugging and does not appear in on-chain event logs.

Exception Handling

TRY / CATCH / FINALLY Opcodes

NeoVM supports structured exception handling:

  • TRY begins a try block, specifying catch and/or finally offsets
  • THROW raises an exception (top of stack becomes the exception object)
  • ENDTRY exits the try block normally, jumping to the end offset
  • ENDFINALLY exits the finally block; if an exception is pending, it is rethrown

Exception Propagation

  1. When THROW is executed, the VM searches the current context's exception handler stack
  2. If a catch handler is found, execution jumps to the catch offset with the exception on the stack
  3. If a finally handler exists, it executes before propagation continues
  4. If no handler is found in the current context, the exception propagates to the caller
  5. ENDFINALLY rethrows any pending exception that was not handled by the catch block

Gas Effects

Gas continues to be consumed during exception handling. The TRY, ENDTRY, and ENDFINALLY opcodes each cost 1 gas unit. Stack unwinding during propagation does not have additional gas cost beyond the opcodes executed.

Native Contract Integration

The embedded runtime includes a registry of Neo N3 native contracts:

ContractDescription
NEONEO governance token
GASGAS utility token
ContractManagementDeploy, update, and query contracts
PolicyNetwork fee policies
OracleOracle request service
RoleManagementNode role management
NotaryNotary service
TreasuryTreasury management
LedgerBlockchain data access
CryptoLibCryptographic functions
StdLibStandard library utilities

ContractManagement

The embedded runtime maintains a stateful in-memory contract registry that supports:

  • Deploy -- register a new contract with NEF and manifest
  • Update -- update an existing contract (increments update counter)
  • GetContract -- query contract metadata by hash

This registry persists within a single test execution and is used by both syscall-based and native contract calls.

Embedded Runtime vs Production Neo N3

What the Embedded Runtime Provides

  • Full NeoVM opcode execution with correct stack semantics
  • Storage read/write/find with iterator support
  • Event notification capture
  • Gas tracking (approximate)
  • Exception handling with try/catch/finally
  • Native contract call routing
  • Crypto operations (SHA256, RIPEMD160, Keccak256, CheckSig)

Differences from Production

AspectEmbedded RuntimeProduction Neo N3
StorageIn-memory, ephemeralPersistent on-chain
Gas costs~85% accurateExact per specification
Blockchain dataStub/placeholder valuesReal chain state
Signature verificationBest-effort secp256k1Full Neo crypto stack
Iterator behaviorMaterialized (eager)Streaming (lazy)
ByteString vs BufferBoth treated as byte arrayDistinct mutation semantics
OracleReturns deterministic pseudo IDReal oracle network
Network stateFixed test valuesLive network data

When to Use Neo-Express

Use the embedded runtime for:

  • Unit testing individual contract functions
  • Verifying compilation output correctness
  • Quick iteration during development

Use Neo-Express or testnet for:

  • Integration testing with multiple contracts
  • Accurate gas measurement
  • Testing with real blockchain state
  • Verifying oracle and cross-contract interactions
  • Pre-mainnet validation

See Also

MIT Licensed