Skip to main content

Storing Data on OP_NET

Managing data efficiently is a cornerstone of smart contract development. OP_NET offers a variety of classes and methods to handle persistent storage, optimized for performance and scalability. This guide provides an overview of storage concepts, available classes, and best practices for working with data on OP_NET.

Key Concepts
  • Array Usage and Performance Considerations
    • Arrays may not be optimal for performance in OP_NET contracts.
    • Storage classes provide more efficient alternatives for specific use cases, especially for large or complex datasets.

  • Native Closest Pointer Search
    • The TickBitmap class offers a high-performance method for retrieving the next initialized storage pointer.
    • Ideal for applications like order books, staking systems, and time-sensitive triggers.

Storage Classes on OP_NET

OP_NET provides specialized classes for managing different types of data in contract storage:

1. StoredBoolean

  • Stores boolean values in a storage slot.
  • Optimized for boolean logic with minimal storage overhead.
Usage Example
import {
Blockchain,
OP_NET,
StoredBoolean,
} from "@btc-vision/btc-runtime/runtime";

class MyContract extends OP_NET {
private isActive: StoredBoolean = new StoredBoolean(
Blockchain.nextPointer,
false
);

public toggleActive(): void {
this.isActive.value = !this.isActive.value;
}

public getActiveState(): bool {
return this.isActive.value;
}
}

2. StoredString

  • Manages string data with support for splitting across multiple slots for long strings.
Usage Example
import {
Blockchain,
OP_NET,
StoredString,
} from "@btc-vision/btc-runtime/runtime";

class MyContract extends OP_NET {
private storedName: StoredString = new StoredString(
Blockchain.nextPointer,
"default"
);

public setName(name: string): void {
this.storedName.value = name;
}

public getName(): string {
return this.storedName.value;
}
}

3. StoredU256

  • Handles u256 values with built-in arithmetic operations and storage management.
Usage Example
import {
Blockchain,
OP_NET,
StoredU256,
} from "@btc-vision/btc-runtime/runtime";
import { u256 } from "as-bignum/assembly";

class MyContract extends OP_NET {
private storedAmount: StoredU256 = new StoredU256(
Blockchain.nextPointer,
u256.fromU32(0),
u256.Zero
);

public addAmount(amount: u256): void {
this.storedAmount.add(amount);
}

public getAmount(): u256 {
return this.storedAmount.value;
}
}

4. AddressMemoryMap

  • Maps an address to a u256 value, useful for creating key-value stores.
Usage Example
import {
Address,
AddressMemoryMap,
Blockchain,
OP_NET,
} from "@btc-vision/btc-runtime/runtime";
import { u256 } from "as-bignum/assembly";

class MyContract extends OP_NET {
private balances: AddressMemoryMap<u256> = new AddressMemoryMap(
Blockchain.nextPointer,
u256.Zero
);

public setBalance(address: Address, balance: u256): void {
this.balances.set(address, balance);
}

public getBalance(address: Address): u256 {
return this.balances.get(address);
}
}

5. MultiAddressMemoryMap

  • Extends AddressMemoryMap to allow mapping multiple keys to values, ideal for multi-dimensional mappings.
Usage Example
import {
Address,
Blockchain,
MultiAddressMemoryMap,
OP_NET,
Uint8ArrayMerger,
} from "@btc-vision/btc-runtime/runtime";
import { u256 } from "as-bignum/assembly";

class MyContract extends OP_NET {
private allowances: MultiAddressMemoryMap<u256> = new MultiAddressMemoryMap(
Blockchain.nextPointer,
u256.Zero
);

public setAllowance(owner: Address, spender: Address, amount: u256): void {
const ownerData =
this.allowances.get(owner) ||
new Uint8ArrayMerger(owner, Blockchain.nextPointer, u256.Zero);
ownerData.set(spender, amount);
this.allowances.set(owner, ownerData);
}

public getAllowance(owner: Address, spender: Address): u256 {
const ownerData = this.allowances.get(owner);
if (!ownerData) {
return u256.Zero;
}
return ownerData.get(spender) || u256.Zero;
}
}

6. Serializable

  • Used for managing complex data structures by serializing them into chunks stored across multiple slots.
Usage Example
import {
BytesReader,
BytesWriter,
MemorySlotPointer,
Serializable,
} from "@btc-vision/btc-runtime/runtime";
import { u256 } from "as-bignum/assembly";

class ComplexData extends Serializable {
private data: u256;

constructor(pointer: u16, subPointer: MemorySlotPointer, data: u256) {
super(pointer, subPointer);
this.data = data;
}

public writeToBuffer(): BytesWriter {
const writer = new BytesWriter(32);
writer.writeU256(this.data);
return writer;
}

public readFromBuffer(reader: BytesReader): void {
this.data = reader.readU256();
}

public get chunkCount(): number {
throw new Error("Method not implemented.");
}

public exists(): boolean {
throw new Error("Method not implemented.");
}
}

The TickBitmap class provides efficient pointer management and storage access.

Usage Example
function nextInitializedTick(
tickIndex: u64,
valueAtLeast: u256,
lte: boolean
): Potential<Tick> {
const storagePointer = TickBitmap.getStoragePointer(this.token, tickIndex);
const nextStoragePointer = Blockchain.getNextPointerGreaterThan(
storagePointer,
valueAtLeast,
lte
);

if (nextStoragePointer.isZero()) return null;

return new Tick(storagePointer, valueAtLeast, nextStoragePointer);
}

Best Practices

  • Leverage StoredBoolean, StoredString, and other classes for optimized storage management.
  • Always validate inputs and outputs to prevent storage corruption.
  • Use compact and efficient storage methods to reduce transaction fees.
  • Utilize TickBitmap for efficient navigation of complex datasets.