How Smart Contract Storage Contexts Enable Persistent Key-Value Storage in Neo

Neo smart contracts use immutable StorageContext structs as lightweight handles that map contract-specific keys to the blockchain's persistent SnapshotCache, enabling durable state that survives across blocks.

Smart contract storage contexts provide the foundational mechanism for persistent key-value storage in the Neo blockchain. In the neo-project/neo repository, these contexts act as secure gateways that isolate contract data while ensuring atomic commits to the underlying blockchain state. Understanding how StorageContext operates reveals the architecture behind durable smart contract storage.

What Is a StorageContext?

A StorageContext is an immutable struct defined in StorageContext.cs that serves as a capability token for storage operations. It encapsulates two critical fields:

  • Id: The internal identifier of the contract that owns the data, ensuring storage isolation between contracts.
  • IsReadOnly: A boolean flag indicating whether the context permits only read operations.

The context itself contains no actual data. Instead, it functions as a lightweight handle that the Virtual Machine passes to the storage engine (ApplicationEngine), which uses the context to construct unique storage keys and authorize state modifications.

How StorageContext Enables Persistent Storage

When a contract invokes storage operations, the StorageContext triggers a multi-layer persistence mechanism that guarantees atomic, durable writes to the blockchain state.

Building the Composite StorageKey

The engine combines the context's Id with the user-provided byte-array key to create a StorageKey (Id + key). This composite key, implemented in StorageKey.cs, serves as the exact index stored in the underlying SnapshotCache—the persistent state layer of the blockchain. This design ensures that each contract operates within its own namespaced storage domain, preventing key collisions between different contracts.

Interacting with SnapshotCache

For every storage operation, ApplicationEngine.Storage.cs executes a consistent workflow:

  1. Creates a StorageKey from the context's Id and the supplied key.
  2. Accesses the SnapshotCache using methods like TryGet, GetAndChange, or Delete to retrieve or modify the corresponding StorageItem.
  3. Returns or mutates the StorageItem, which holds the raw byte-array value.

Because SnapshotCache commits to the blockchain at the end of each block, every write performed through a StorageContext becomes permanent and visible to all future contract invocations.

Read-Only vs. Read-Write Contexts

Neo enforces strict access controls through the IsReadOnly property, preventing accidental state corruption during query operations.

Contracts obtain read-write contexts via System.Storage.GetContext (implemented in ApplicationEngine.Storage.cs lines 97-101), while read-only contexts are acquired through System.Storage.GetReadOnlyContext (lines 113-120). The engine enforces the read-only restriction at lines 34-35 of ApplicationEngine.Storage.cs, where Put and Delete operations throw an ArgumentException if context.IsReadOnly evaluates to true.

This architectural guarantee ensures that contracts can safely expose query methods without risking unintended state modifications.

Core Implementation Files

The storage context architecture spans several critical files in the Neo repository:

File Purpose
StorageContext.cs Defines the immutable struct holding Id and IsReadOnly fields.
StorageKey.cs Encodes the composite key (Id + userKey) used for persistence.
StorageItem.cs Represents stored values and provides lazy conversion helpers.
ApplicationEngine.Storage.cs Implements interop methods (Get, Put, Delete, Find) that use StorageContext to manipulate the persistent SnapshotCache.
Iterators/StorageIterator.cs Supports Storage.Find by iterating over matching key-value pairs with various filtering options.

Practical Code Examples

Basic Read-Write Operations

Contracts interact with persistent storage by obtaining a context and performing standard CRUD operations:

public static void Main()
{
    // Obtain a mutable context for the current contract
    var ctx = Storage.GetContext();                     // System.Storage.GetContext

    // Write a value (key: "counter", value: 1)
    Storage.Put(ctx, "counter".ToByteArray(), new byte[] { 1 });

    // Read the value back
    var raw = Storage.Get(ctx, "counter".ToByteArray());
    if (raw != null) {
        var value = raw.Value.Span[0];  // value == 1
    }
}

Implementation references: Storage.GetContext → ApplicationEngine.Storage.cs lines 97-101; Storage.Put → lines 122-129; Storage.Get → lines 146-152.

Read-Only Access Patterns

To prevent accidental state modifications during queries, use the read-only context:

public static void Query()
{
    var readOnlyCtx = Storage.GetReadOnlyContext();   // System.Storage.GetReadOnlyContext

    var data = Storage.Get(readOnlyCtx, "someKey".ToByteArray());

    // The following would throw ArgumentException because the context is read-only:
    // Storage.Put(readOnlyCtx, "someKey".ToByteArray(), new byte[] { 0 });
}

Implementation references: GetReadOnlyContext → ApplicationEngine.Storage.cs lines 113-120; enforcement in Put → lines 34-35.

Enumerating Storage Entries

The Find method enables prefix-based iteration over contract storage:

public static IEnumerable<(byte[] key, StackItem value)> FindAll()
{
    var ctx = Storage.GetReadOnlyContext();
    var iterator = Storage.Find(ctx,
                               "user:".ToByteArray(),          // prefix
                               FindOptions.ValuesOnly);        // only return values

    while (iterator.Next())
    {
        var value = iterator.Value(null);   // value is deserialized if needed
        var key   = iterator.Key;          // raw key bytes (prefix stripped if option set)
        yield return (key.ToArray(), value);
    }
}

Implementation references: Find implementation (handling of FindOptions) → ApplicationEngine.Storage.cs lines 176-186; iterator logic → StorageIterator.cs lines 45-64.

Summary

  • StorageContext is an immutable struct (Id, IsReadOnly) that acts as a capability token for storage operations, defined in StorageContext.cs.
  • Persistence mechanism combines the context's Id with user keys to create composite StorageKey objects that index into the blockchain's SnapshotCache.
  • Access control is enforced at the engine level: read-only contexts obtained via System.Storage.GetReadOnlyContext throw ArgumentException on Put or Delete operations.
  • Core files include StorageContext.cs, StorageKey.cs, StorageItem.cs, and ApplicationEngine.Storage.cs, which together implement the persistent key-value store accessible through Neo smart contracts.

Frequently Asked Questions

What is the difference between Storage.GetContext and Storage.GetReadOnlyContext?

Storage.GetContext returns a mutable StorageContext that allows both reading and writing to contract storage, while Storage.GetReadOnlyContext returns a context with IsReadOnly set to true. The read-only variant prevents accidental state modifications because ApplicationEngine throws an ArgumentException if you attempt to call Put or Delete with a read-only context.

How does Neo prevent key collisions between different smart contracts?

Neo prevents key collisions by prefixing every user-provided key with the contract's unique Id stored in the StorageContext. When a contract calls a storage method, ApplicationEngine creates a StorageKey by combining context.Id + key. This composite key ensures that contractA's "balance" key is physically distinct from contractB's "balance" key in the underlying SnapshotCache.

What happens to storage changes if a smart contract execution fails?

Storage changes made through a StorageContext are only committed to the blockchain state when the entire transaction is successfully executed and the block is persisted. SnapshotCache acts as a transactional layer that accumulates changes during execution. If the contract throws an exception or fails validation, the snapshot is discarded, ensuring that partial or failed executions never corrupt the persistent storage state.

Can smart contracts access the storage of other contracts?

Yes, smart contracts can access other contracts' storage if they obtain a valid StorageContext for that contract. However, the context must be acquired through proper interop methods that verify permissions. The Id field in the StorageContext determines which contract's storage namespace is accessed, and the VM enforces that contracts cannot forge contexts for arbitrary contracts without authorization.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →