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

> Discover how Neo smart contract storage contexts provide persistent key-value storage by mapping contract keys to the blockchain's durable SnapshotCache.

- Repository: [The Neo Project/neo](https://github.com/neo-project/neo)
- Tags: deep-dive
- Published: 2026-03-08

---

**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`](https://github.com/neo-project/neo/blob/main/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`](https://github.com/neo-project/neo/blob/main/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`](https://github.com/neo-project/neo/blob/main/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`](https://github.com/neo-project/neo/blob/main/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`](https://github.com/neo-project/neo/blob/main/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`](https://github.com/neo-project/neo/blob/main/StorageContext.cs)** | Defines the immutable struct holding `Id` and `IsReadOnly` fields. |
| **[`StorageKey.cs`](https://github.com/neo-project/neo/blob/main/StorageKey.cs)** | Encodes the composite key (`Id + userKey`) used for persistence. |
| **[`StorageItem.cs`](https://github.com/neo-project/neo/blob/main/StorageItem.cs)** | Represents stored values and provides lazy conversion helpers. |
| **[`ApplicationEngine.Storage.cs`](https://github.com/neo-project/neo/blob/main/ApplicationEngine.Storage.cs)** | Implements interop methods (`Get`, `Put`, `Delete`, `Find`) that use `StorageContext` to manipulate the persistent `SnapshotCache`. |
| **[`Iterators/StorageIterator.cs`](https://github.com/neo-project/neo/blob/main/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:

```csharp
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`](https://github.com/neo-project/neo/blob/main/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:

```csharp
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`](https://github.com/neo-project/neo/blob/main/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:

```csharp
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`](https://github.com/neo-project/neo/blob/main/ApplicationEngine.Storage.cs) lines 176-186; iterator logic → [`StorageIterator.cs`](https://github.com/neo-project/neo/blob/main/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`](https://github.com/neo-project/neo/blob/main/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`](https://github.com/neo-project/neo/blob/main/StorageContext.cs), [`StorageKey.cs`](https://github.com/neo-project/neo/blob/main/StorageKey.cs), [`StorageItem.cs`](https://github.com/neo-project/neo/blob/main/StorageItem.cs), and [`ApplicationEngine.Storage.cs`](https://github.com/neo-project/neo/blob/main/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.