@btc-vision/btc-runtime
Version:
Bitcoin L1 Smart Contract Runtime for OP_NET. Build decentralized applications on Bitcoin using AssemblyScript and WebAssembly. Fully audited.
939 lines (763 loc) • 29 kB
Markdown
# Storage System
OP_NET uses a pointer-based storage system that provides deterministic, secure, and efficient data persistence on Bitcoin L1. This guide explains how storage works and how to use it effectively.
## Overview
Unlike Solidity where storage is implicitly managed, OP_NET requires explicit pointer allocation for all persistent data. This design provides:
- **Deterministic storage locations** via SHA256 hashing
- **Collision-free addressing** through unique pointer combinations
- **Efficient access** with optimized read/write patterns
- **Verifiable state proofs** for cross-chain validation
## How Storage Works
### Storage Keys
Every storage location is identified by a unique key generated from:
```
StorageKey = SHA256(pointer || subPointer)
```
Where:
- `pointer` is a `u16` (0-65535) identifying the storage slot type
- `subPointer` is a `u256` for sub-indexing (e.g., addresses in a mapping)
```typescript
// Example: Balance storage for address 0xABC...
pointer = 3 // balances mapping pointer
subPointer = 0xABC... // the address
storageKey = SHA256(3 || 0xABC...)
```
### Storage Key Derivation Flow
```mermaid
---
config:
theme: dark
---
flowchart LR
subgraph Input["Developer Input"]
DEV["Contract Code"]
DEV -->|"Declares"| VAR1["totalSupplyPointer: u16"]
DEV -->|"Declares"| VAR2["balancesPointer: u16"]
end
subgraph Allocation["Runtime Allocation"]
BC["Blockchain.nextPointer"]
VAR1 -->|"Calls"| BC
VAR2 -->|"Calls"| BC
BC -->|"Returns 0"| P0["Pointer 0"]
BC -->|"Returns 1"| P1["Pointer 1"]
end
subgraph SimpleKey["Simple Value Key"]
P0 --> EMPTY["EMPTY_POINTER<br/>(u256.Zero)"]
EMPTY --> HASH1["SHA256(0 || 0x00...00)"]
HASH1 --> KEY1[("Storage Key<br/>for totalSupply")]
end
subgraph MappingKey["Mapping Key (balances)"]
P1 --> ADDR["User Address<br/>0xABC..."]
ADDR --> HASH2["SHA256(1 || 0xABC...)"]
HASH2 --> KEY2[("Storage Key<br/>for balances[0xABC]")]
end
```
### Storage Layout
```mermaid
---
config:
theme: dark
---
flowchart LR
CS[Contract Storage] --> P0["Pointer 0: totalSupply"]
CS --> P1["Pointer 1: name"]
CS --> P2["Pointer 2: symbol"]
CS --> P3["Pointer 3: balances mapping"]
CS --> P4["Pointer 4: allowances mapping"]
P3 --> S1["subPointer 0xAAA -> balance"]
P3 --> S2["subPointer 0xBBB -> balance"]
P3 --> S3["..."]
P4 --> N1["owner+spender hash -> allowance"]
```
## Solidity vs OP_NET Storage Model
In Solidity, storage slots are assigned implicitly by the compiler. In OP_NET, you explicitly allocate pointers at runtime.
### Quick Reference Table
| Feature | Solidity | OP_NET |
|---------|----------|-------|
| Storage slot assignment | Implicit (compiler) | Explicit (`Blockchain.nextPointer`) |
| Hash function | keccak256 | SHA256 |
| Mapping type | `mapping(K => V)` | `StoredMapU256`, `AddressMemoryMap`, `MapOfMap<T>` |
| Array type | `T[]` | `StoredU256Array`, `StoredAddressArray`, etc. |
| Simple value | `uint256 public x;` | `StoredU256` |
| String storage | `string public s;` | `StoredString` |
| Boolean storage | `bool public b;` | `StoredBoolean` |
| Address storage | `address public a;` | `StoredAddress` |
| Nested mapping | `mapping(a => mapping(b => c))` | `MapOfMap<T>` |
| Default uint value | `0` | `u256.Zero` |
| Maximum slots/pointers | ~2^256 | 65,535 (`u16`) |
### Type Mapping Reference
| Solidity Type | OP_NET Equivalent | Notes |
|---------------|------------------|-------|
| `uint256` | `StoredU256` | 32 bytes |
| `uint64` (packed) | `StoredU64` | Stores up to 4 u64 values in one slot |
| `uint32` (packed) | `StoredU32` | Stores up to 8 u32 values in one slot |
| `bool` | `StoredBoolean` | 1 byte |
| `string` | `StoredString` | Variable length |
| `address` | `StoredAddress` | 32 bytes |
| `uint256[]` | `StoredU256Array` | Dynamic array |
| `address[]` | `StoredAddressArray` | Dynamic array |
| `mapping(address => uint256)` | `AddressMemoryMap` | Address-keyed |
| `mapping(uint256 => uint256)` | `StoredMapU256` | u256-keyed |
| `mapping(address => mapping(address => uint256))` | `MapOfMap<u256>` | Two-level nesting |
### Side-by-Side Code Comparison
```solidity
// Solidity - Implicit slot assignment
contract Token {
uint256 public totalSupply; // slot 0 (assigned by compiler)
string public name; // slot 1 (assigned by compiler)
mapping(address => uint256) balances; // slot 2 (assigned by compiler)
}
```
```typescript
// OP_NET - Explicit pointer allocation
export class Token extends OP_NET {
private readonly totalSupplyPointer: u16 = Blockchain.nextPointer; // ~0 (allocated at runtime)
private readonly namePointer: u16 = Blockchain.nextPointer; // ~1 (allocated at runtime)
private readonly balancesPointer: u16 = Blockchain.nextPointer; // ~2 (allocated at runtime)
}
```
```mermaid
---
config:
theme: dark
---
flowchart LR
subgraph SolidityFlow["Solidity (Ethereum)"]
S_USER[("👤 User")] -->|"Sends ETH + calldata"| S_TX["Ethereum Transaction"]
S_TX -->|"EVM executes"| S_CONTRACT["Smart Contract"]
subgraph S_Storage["Storage (Implicit)"]
S_COMPILER["Compiler assigns slots<br/>at compile time"]
S_SLOT0["Slot 0: totalSupply"]
S_SLOT1["Slot 1: balances"]
S_SLOT2["Slot 2: allowances"]
S_COMPILER -.->|"Hidden from dev"| S_SLOT0
S_COMPILER -.->|"Hidden from dev"| S_SLOT1
S_COMPILER -.->|"Hidden from dev"| S_SLOT2
end
S_CONTRACT -->|"keccak256(slot.key)"| S_Storage
S_CONTRACT -->|"CAN hold ETH"| S_CUSTODY["Contract Custody<br/>address(this).balance"]
end
subgraph OPNetFlow["OP_NET (Bitcoin L1)"]
O_USER[("👤 User")] -->|"Signs Bitcoin TX"| O_TX["Bitcoin Transaction"]
O_TX -->|"WASM executes"| O_CONTRACT["Smart Contract"]
subgraph O_Storage["Storage (Explicit)"]
O_RUNTIME["Runtime allocates ptrs<br/>at execution time"]
O_PTR0["Pointer 0: totalSupplyPointer"]
O_PTR1["Pointer 1: balancesPointer"]
O_PTR2["Pointer 2: allowancesPointer"]
O_RUNTIME -->|"Dev controls"| O_PTR0
O_RUNTIME -->|"Dev controls"| O_PTR1
O_RUNTIME -->|"Dev controls"| O_PTR2
end
O_CONTRACT -->|"SHA256(ptr || subPtr)"| O_Storage
O_CONTRACT -->|"CANNOT hold BTC"| O_VERIFY["Verify-Only Pattern<br/>blockchain.tx.outputs"]
O_VERIFY -->|"Validates"| O_TX
end
subgraph KeyDiff["Critical Differences"]
DIFF1["Custody: Solidity holds funds,<br/>OP_NET verifies outputs"]
DIFF2["Storage: Solidity implicit slots,<br/>OP_NET explicit pointers"]
DIFF3["Hash: Solidity keccak256,<br/>OP_NET SHA256"]
DIFF4["Execution: Solidity EVM,<br/>OP_NET WASM"]
end
```
> **Important:** Do NOT use AssemblyScript's built-in Map for blockchain storage. See [CRITICAL: Map Implementation Warning](../storage/stored-maps.md#critical-map-implementation-warning) for details.
## Pointer Allocation
### Allocating Pointers
Use `Blockchain.nextPointer` to allocate unique pointers:
```typescript
import { Blockchain } from '@btc-vision/btc-runtime/runtime';
@final
export class MyContract extends OP_NET {
// Each call to nextPointer returns a unique u16
private readonly totalSupplyPointer: u16 = Blockchain.nextPointer;
private readonly namePointer: u16 = Blockchain.nextPointer;
private readonly balancesPointer: u16 = Blockchain.nextPointer;
private readonly allowancesPointer: u16 = Blockchain.nextPointer;
// ...
}
```
```mermaid
---
config:
theme: dark
---
sequenceDiagram
participant User
participant Bitcoin as Bitcoin L1
participant OP_NET as OP_NET Node
participant WASM as WASM Runtime
participant Contract
participant Blockchain as Blockchain API
participant Storage as Storage System
User->>Bitcoin: Broadcast Transaction
Bitcoin->>OP_NET: New Block with TX
OP_NET->>WASM: Execute Contract
WASM->>Contract: Instantiate
Contract->>Blockchain: nextPointer
Blockchain-->>Contract: 0 (totalSupply)
Contract->>Blockchain: nextPointer
Blockchain-->>Contract: 1 (name)
Contract->>Blockchain: nextPointer
Blockchain-->>Contract: 2 (balances)
Contract->>Blockchain: nextPointer
Blockchain-->>Contract: 3 (allowances)
Contract->>Storage: Read/Write with pointers
Storage-->>Contract: Data
Contract-->>WASM: Execution Result
WASM-->>OP_NET: State Changes
OP_NET->>OP_NET: Update State Root
```
## System Architecture
The following diagram shows how storage fits into the overall OP_NET architecture:
```mermaid
---
config:
theme: dark
---
flowchart TD
subgraph UserLayer["User Layer"]
USER[("👤 User")]
end
subgraph BitcoinL1["Bitcoin L1"]
BTC_TX["Bitcoin Transaction"]
BTC_BLOCK["Bitcoin Block"]
UTXO["UTXOs"]
end
subgraph OPNetConsensus["OP_NET Consensus Layer"]
INDEXER["OP_NET Nodes"]
WASM["WASM Runtime"]
EPOCH["Epoch Mining<br/>SHA1 PoW"]
CHECKPOINT["State Checksum<br/>Root Hash"]
end
subgraph Contract["Smart Contract"]
ENTRY["Contract Entry Point"]
LOGIC["Business Logic"]
VERIFY["Output Verification<br/>blockchain.tx.outputs"]
PTR_ALLOC["Pointer Allocation<br/>Blockchain.nextPointer"]
end
subgraph StorageSystem["Storage System"]
PTR["Pointer (u16)<br/>0-65535 slots"]
SUBPTR["SubPointer (u256)<br/>mapping keys"]
HASH["SHA256(ptr || subPtr)"]
STORAGE[("Persistent State<br/>Key-Value Store")]
PTR --> HASH
SUBPTR --> HASH
HASH --> STORAGE
end
USER -->|"Signs & Broadcasts"| BTC_TX
BTC_TX -->|"Included in"| BTC_BLOCK
BTC_BLOCK -->|"Parsed by"| INDEXER
INDEXER -->|"Executes in"| WASM
WASM -->|"Runs"| ENTRY
ENTRY --> LOGIC
LOGIC -->|"Non-custodial verify"| VERIFY
LOGIC -->|"Allocates"| PTR_ALLOC
PTR_ALLOC -->|"Returns u16"| PTR
LOGIC -->|"Read/Write"| STORAGE
INDEXER -->|"Every 20 blocks"| EPOCH
EPOCH -->|"Produces"| CHECKPOINT
CHECKPOINT -->|"Anchors to"| BTC_BLOCK
VERIFY -->|"Validates"| UTXO
BTC_TX -->|"Creates/Spends"| UTXO
```
## Storage Types
OP_NET provides typed storage classes for common data types:
### Primitive Storage
```typescript
import {
StoredU256,
StoredU64,
StoredU32,
StoredBoolean,
StoredString,
StoredAddress,
EMPTY_POINTER,
} from '@btc-vision/btc-runtime/runtime';
// Usage
private readonly totalSupplyPointer: u16 = Blockchain.nextPointer;
private readonly _totalSupply: StoredU256 = new StoredU256(
this.totalSupplyPointer,
EMPTY_POINTER
);
// Read value
const supply = this._totalSupply.value;
// Write value
this._totalSupply.value = newSupply;
```
### Array Storage
```typescript
import {
ABIDataTypes,
BytesWriter,
Calldata,
StoredU256Array,
StoredU128Array,
StoredAddressArray,
StoredBooleanArray,
} from '@btc-vision/btc-runtime/runtime';
// Usage
private readonly holdersPointer: u16 = Blockchain.nextPointer;
private readonly holders: StoredAddressArray = new StoredAddressArray(this.holdersPointer, EMPTY_POINTER);
// Operations
@method({ name: 'holder', type: ABIDataTypes.ADDRESS })
public addHolder(calldata: Calldata): BytesWriter {
const newHolder = calldata.readAddress();
this.holders.push(newHolder);
this.holders.save(); // Commit changes
return new BytesWriter(0);
}
const holder = this.holders.get(index);
const length = this.holders.getLength();
this.holders.deleteLast();
this.holders.save();
```
### Map Storage
```typescript
import { u256 } from '@btc-vision/as-bignum/assembly';
import {
Address,
StoredMapU256,
AddressMemoryMap,
} from '@btc-vision/btc-runtime/runtime';
// Simple mapping
private readonly balancesPointer: u16 = Blockchain.nextPointer;
private readonly balances: StoredMapU256 = new StoredMapU256(this.balancesPointer);
// Address-keyed mapping (default value is u256.Zero)
private readonly balanceMap: AddressMemoryMap;
public constructor() {
super();
this.balanceMap = new AddressMemoryMap(this.balancesPointer);
}
```
## Storage Patterns
### Common Patterns Comparison
| Pattern | Solidity | OP_NET |
|---------|----------|-------|
| Increment counter | `counter++;` | `counter.value = SafeMath.add(counter.value, u256.One);` |
| Read balance | `balances[addr]` | `balanceOf.get(addr)` |
| Write balance | `balances[addr] = x` | `balanceOf.set(addr, x)` |
| Check approval | `allowances[owner][spender]` | `allowances.get(owner).get(spender)` |
| Set approval | `allowances[owner][spender] = x` | `ownerMap = allowances.get(owner); ownerMap.set(spender, x); allowances.set(owner, ownerMap);` |
| Array push | `arr.push(x)` | `arr.push(x); arr.save()` |
| Array length | `arr.length` | `arr.getLength()` |
| Array access | `arr[i]` | `arr.get(i)` |
| Require/revert | `require(cond, "msg")` | `if (!cond) throw new Revert("msg")` |
| Get sender | `msg.sender` | `Blockchain.tx.sender` |
| Get origin | `tx.origin` | `Blockchain.tx.origin` |
| Block number | `block.number` | `Blockchain.block.number` |
| Block timestamp | `block.timestamp` | `Blockchain.block.medianTime` |
### Simple Value
```typescript
import { u256 } from '@btc-vision/as-bignum/assembly';
import {
Blockchain,
SafeMath,
StoredU256,
} from '@btc-vision/btc-runtime/runtime';
// Solidity: uint256 public counter;
private readonly counterPointer: u16 = Blockchain.nextPointer;
private readonly counter: StoredU256 = new StoredU256(this.counterPointer, EMPTY_POINTER);
// Increment
this.counter.value = SafeMath.add(this.counter.value, u256.One);
```
### Mapping (address => uint256)
```typescript
import { u256 } from '@btc-vision/as-bignum/assembly';
import {
ABIDataTypes,
Address,
AddressMemoryMap,
SafeMath,
} from '@btc-vision/btc-runtime/runtime';
// Solidity: mapping(address => uint256) public balances;
private readonly balancesPointer: u16 = Blockchain.nextPointer;
private readonly balanceOf: AddressMemoryMap;
public constructor() {
super();
this.balanceOf = new AddressMemoryMap(this.balancesPointer);
}
// Get balance
@method({ name: 'address', type: ABIDataTypes.ADDRESS })
@returns({ name: 'balance', type: ABIDataTypes.UINT256 })
public getBalance(address: Address): u256 {
return this.balanceOf.get(address);
}
// Set balance (using SafeMath for operations)
@method(
{ name: 'address', type: ABIDataTypes.ADDRESS },
{ name: 'amount', type: ABIDataTypes.UINT256 }
)
public setBalance(address: Address, amount: u256): void {
this.balanceOf.set(address, amount);
}
```
### Nested Mapping (address => address => uint256)
For nested mappings like allowances, use `MapOfMap<T>` which provides a two-step get/set pattern:
```typescript
import { u256 } from '@btc-vision/as-bignum/assembly';
import {
ABIDataTypes,
Address,
MapOfMap,
Nested,
} from '@btc-vision/btc-runtime/runtime';
// Solidity: mapping(address => mapping(address => uint256)) public allowances;
private readonly allowancesPointer: u16 = Blockchain.nextPointer;
private readonly allowances: MapOfMap<u256>;
public constructor() {
super();
this.allowances = new MapOfMap<u256>(this.allowancesPointer);
}
// Getting nested value - two-step process
@method(
{ name: 'owner', type: ABIDataTypes.ADDRESS },
{ name: 'spender', type: ABIDataTypes.ADDRESS }
)
@returns({ name: 'allowance', type: ABIDataTypes.UINT256 })
public getAllowance(owner: Address, spender: Address): u256 {
const ownerMap = this.allowances.get(owner); // Returns Nested<u256>
return ownerMap.get(spender); // Returns u256
}
// Setting nested value - get, modify, commit back
protected setAllowance(owner: Address, spender: Address, amount: u256): void {
const ownerMap = this.allowances.get(owner);
ownerMap.set(spender, amount);
this.allowances.set(owner, ownerMap); // Commit back
}
```
### Struct-like Storage
```typescript
// Solidity:
// struct User { address addr; uint256 balance; bool active; }
// mapping(uint256 => User) public users;
// OP_NET: Use multiple pointers or encode into u256
private readonly userAddressPointer: u16 = Blockchain.nextPointer;
private readonly userBalancePointer: u16 = Blockchain.nextPointer;
private readonly userActivePointer: u16 = Blockchain.nextPointer;
private readonly userAddresses: StoredMapU256 = new StoredMapU256(this.userAddressPointer);
private readonly userBalances: StoredMapU256 = new StoredMapU256(this.userBalancePointer);
private readonly userActives: StoredMapU256 = new StoredMapU256(this.userActivePointer);
```
### Complete ERC-20 Style Comparison
Here's a side-by-side comparison of a complete token contract:
**Solidity:**
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleToken {
string public name;
string public symbol;
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(string memory _name, string memory _symbol, uint256 _initialSupply) {
name = _name;
symbol = _symbol;
totalSupply = _initialSupply;
balanceOf[msg.sender] = _initialSupply;
}
function transfer(address to, uint256 amount) external returns (bool) {
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
function approve(address spender, uint256 amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
require(allowance[from][msg.sender] >= amount, "Insufficient allowance");
require(balanceOf[from] >= amount, "Insufficient balance");
allowance[from][msg.sender] -= amount;
balanceOf[from] -= amount;
balanceOf[to] += amount;
emit Transfer(from, to, amount);
return true;
}
}
```
**OP_NET:**
```typescript
import { u256 } from '@btc-vision/as-bignum/assembly';
import {
Address,
AddressMemoryMap,
Blockchain,
BytesWriter,
Calldata,
MapOfMap,
OP_NET,
Revert,
SafeMath,
StoredString,
StoredU256,
EMPTY_POINTER,
} from '@btc-vision/btc-runtime/runtime';
@final
export class SimpleToken extends OP_NET {
// Pointer allocation (equivalent to slot assignment)
private readonly namePointer: u16 = Blockchain.nextPointer;
private readonly symbolPointer: u16 = Blockchain.nextPointer;
private readonly totalSupplyPointer: u16 = Blockchain.nextPointer;
private readonly balancesPointer: u16 = Blockchain.nextPointer;
private readonly allowancesPointer: u16 = Blockchain.nextPointer;
// Storage variables
private readonly _name: StoredString = new StoredString(this.namePointer, 0);
private readonly _symbol: StoredString = new StoredString(this.symbolPointer, 0);
private readonly _decimals: u8 = 18; // Constant value, no storage needed
private readonly _totalSupply: StoredU256 = new StoredU256(this.totalSupplyPointer, EMPTY_POINTER);
private readonly _balanceOf: AddressMemoryMap;
private readonly _allowance: MapOfMap<u256>;
public constructor() {
super();
this._balanceOf = new AddressMemoryMap(this.balancesPointer);
this._allowance = new MapOfMap<u256>(this.allowancesPointer);
}
// Equivalent to Solidity constructor
public override onDeployment(calldata: Calldata): void {
this._name.value = calldata.readString();
this._symbol.value = calldata.readString();
const initialSupply = calldata.readU256();
this._totalSupply.value = initialSupply;
this._balanceOf.set(Blockchain.tx.origin, initialSupply);
}
// function transfer(address to, uint256 amount) external returns (bool)
public transfer(calldata: Calldata): BytesWriter {
const to = calldata.readAddress();
const amount = calldata.readU256();
const sender = Blockchain.tx.sender;
const senderBalance = this._balanceOf.get(sender);
if (senderBalance < amount) {
throw new Revert('Insufficient balance');
}
this._balanceOf.set(sender, SafeMath.sub(senderBalance, amount));
this._balanceOf.set(to, SafeMath.add(this._balanceOf.get(to), amount));
// Emit Transfer event (implementation depends on event system)
const writer = new BytesWriter(1);
writer.writeBoolean(true);
return writer;
}
// function approve(address spender, uint256 amount) external returns (bool)
public approve(calldata: Calldata): BytesWriter {
const spender = calldata.readAddress();
const amount = calldata.readU256();
const sender = Blockchain.tx.sender;
// MapOfMap pattern: get nested, modify, commit back
const senderAllowances = this._allowance.get(sender);
senderAllowances.set(spender, amount);
this._allowance.set(sender, senderAllowances);
const writer = new BytesWriter(1);
writer.writeBoolean(true);
return writer;
}
// function transferFrom(address from, address to, uint256 amount) external returns (bool)
public transferFrom(calldata: Calldata): BytesWriter {
const from = calldata.readAddress();
const to = calldata.readAddress();
const amount = calldata.readU256();
const sender = Blockchain.tx.sender;
// Check allowance
const fromAllowances = this._allowance.get(from);
const currentAllowance = fromAllowances.get(sender);
if (currentAllowance < amount) {
throw new Revert('Insufficient allowance');
}
// Check balance
const fromBalance = this._balanceOf.get(from);
if (fromBalance < amount) {
throw new Revert('Insufficient balance');
}
// Update allowance
fromAllowances.set(sender, SafeMath.sub(currentAllowance, amount));
this._allowance.set(from, fromAllowances);
// Update balances
this._balanceOf.set(from, SafeMath.sub(fromBalance, amount));
this._balanceOf.set(to, SafeMath.add(this._balanceOf.get(to), amount));
const writer = new BytesWriter(1);
writer.writeBoolean(true);
return writer;
}
}
```
## Reading and Writing
```mermaid
---
config:
theme: dark
---
sequenceDiagram
participant TX as Bitcoin Transaction
participant Contract as Contract
participant Logic as Business Logic
participant Storage as Storage System
participant Buffer as Memory Buffer
participant State as Persistent State
Note over TX,State: Read Operation
TX->>Contract: TX Input (User Signs)
Contract->>Logic: Method Call
Logic->>Storage: Get pointer + subPointer
Storage->>Storage: Compute SHA256 key
Storage->>Storage: Blockchain.getStorageAt
Storage-->>Logic: Decode to typed value
Note over TX,State: Write Operation
Logic->>Storage: Get pointer + subPointer
Storage->>Storage: Compute SHA256 key
Storage->>Storage: Encode typed value
Storage->>Buffer: Buffer in memory
Note over TX,State: Verification & Commit
Logic->>TX: Verify TX Outputs (UTXOs)
TX-->>Contract: TX Complete
Buffer->>State: Commit on TX Success
State->>State: Update State Checksum
Note over State: Every 20 blocks: Epoch Root
```
### Read Operations
```typescript
// Read primitive
const value = this._totalSupply.value;
// Read from map
const balance = this.balanceOf.get(address);
// Read from array
const holder = this.holders.get(index);
const length = this.holders.getLength();
```
### Write Operations
```typescript
// Write primitive
this._totalSupply.value = newValue;
// Write to map
this.balanceOf.set(address, newBalance);
// Write to array
this.holders.push(newAddress);
this.holders.set(index, address);
this.holders.save(); // Commit changes
```
### Commit Optimization
For complex operations, delay commits until necessary:
```typescript
import { SafeMath } from '@btc-vision/btc-runtime/runtime';
// Multiple operations without intermediate commits
const currentBalance = this.balanceOf.get(from);
const newBalance = SafeMath.sub(currentBalance, amount);
this.balanceOf.set(from, newBalance); // Value is buffered
// Changes are committed when transaction completes
```
## Default Values
Always provide sensible defaults:
```typescript
// u256 with EMPTY_POINTER
private balance: StoredU256 = new StoredU256(pointer, EMPTY_POINTER);
// String with index 0
private name: StoredString = new StoredString(pointer, 0);
// Boolean with false default
private paused: StoredBoolean = new StoredBoolean(pointer, false);
```
## Storage Limits
| Limit | Value | Notes |
|-------|-------|-------|
| Pointers per contract | 65,535 | `u16` range |
| Array length | ~4 billion | `u32` range (default maxLength configurable) |
| String length | 65,535 bytes | Encoded in storage |
| Sub-pointers | `u256` range | Effectively unlimited |
## Best Practices
### 1. Allocate Pointers in Order
```typescript
// Good: Sequential allocation
private ptr1: u16 = Blockchain.nextPointer;
private ptr2: u16 = Blockchain.nextPointer;
private ptr3: u16 = Blockchain.nextPointer;
// Bad: Gaps or manual assignment
private ptr1: u16 = 0;
private ptr2: u16 = 5; // Gap!
```
### 2. Use Typed Storage
```typescript
// Good: Type-safe storage
private balance: StoredU256 = new StoredU256(ptr, EMPTY_POINTER);
// Avoid: Raw storage access (only for special cases)
import { encodePointer } from '@btc-vision/btc-runtime/runtime';
const pointerHash = encodePointer(ptr, subPtr);
Blockchain.setStorageAt(pointerHash, value);
```
### 3. Initialize in Constructor or onDeployment
```typescript
import { u256 } from '@btc-vision/as-bignum/assembly';
import {
Address,
AddressMemoryMap,
Blockchain,
Calldata,
OP_NET,
} from '@btc-vision/btc-runtime/runtime';
export class MyContract extends OP_NET {
private readonly balancesPointer: u16 = Blockchain.nextPointer;
private readonly balanceOf: AddressMemoryMap;
public constructor() {
super();
// Initialize storage maps in constructor
this.balanceOf = new AddressMemoryMap(this.balancesPointer);
}
public override onDeployment(calldata: Calldata): void {
// Set initial values here
this._totalSupply.value = initialSupply;
}
}
```
### 4. Optimize Storage Access
```mermaid
---
config:
theme: dark
---
flowchart TD
subgraph Bad["Expensive: Multiple Storage Reads"]
LOOP1["for (i = 0; i < 100; i++)"]
LOOP1 --> READ1["Storage Read #1"]
LOOP1 --> READ2["Storage Read #2"]
LOOP1 --> READ3["Storage Read #..."]
LOOP1 --> READ100["Storage Read #100"]
READ1 --> COST1["100x Storage I/O"]
READ2 --> COST1
READ3 --> COST1
READ100 --> COST1
end
subgraph Good["Optimized: Cache and Batch"]
CACHE["Single Storage Read"]
CACHE --> MEM["Cache in Memory"]
MEM --> LOOP2["for (i = 0; i < 100; i++)<br/>Use cached value"]
LOOP2 --> WRITE["Single Storage Write"]
WRITE --> COST2["1x Read + 1x Write"]
end
COST1 -->|"vs"| COST2
```
```typescript
import { SafeMath } from '@btc-vision/btc-runtime/runtime';
// Inefficient: Reading same value multiple times
for (let i = 0; i < 100; i++) {
const balance = this.balanceOf.get(address); // Storage read each time
// ...
}
// Better: Cache the value
const balance = this.balanceOf.get(address); // One storage read
for (let i = 0; i < 100; i++) {
// Use cached balance
// When modifying, use SafeMath
const updated = SafeMath.add(balance, u256.One);
}
```
## Transient Storage
For temporary data that doesn't persist between transactions:
```typescript
// Transient storage is cleared after each transaction
// Useful for reentrancy guards, temporary calculations, etc.
```
See [Advanced Storage](../storage/stored-primitives.md) for transient storage details.
---
**Navigation:**
- Previous: [Blockchain Environment](./blockchain-environment.md)
- Next: [Pointers](./pointers.md)