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.
- The
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.");
}
}
TickBitmap: Native Closest Pointer Search
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.