@btc-vision/btc-runtime 1.10.10 → 1.10.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +258 -137
- package/SECURITY.md +226 -0
- package/docs/README.md +614 -0
- package/docs/advanced/bitcoin-scripts.md +939 -0
- package/docs/advanced/cross-contract-calls.md +579 -0
- package/docs/advanced/plugins.md +1006 -0
- package/docs/advanced/quantum-resistance.md +660 -0
- package/docs/advanced/signature-verification.md +715 -0
- package/docs/api-reference/blockchain.md +729 -0
- package/docs/api-reference/events.md +642 -0
- package/docs/api-reference/op20.md +902 -0
- package/docs/api-reference/op721.md +819 -0
- package/docs/api-reference/safe-math.md +510 -0
- package/docs/api-reference/storage.md +840 -0
- package/docs/contracts/op-net-base.md +786 -0
- package/docs/contracts/op20-token.md +687 -0
- package/docs/contracts/op20s-signatures.md +614 -0
- package/docs/contracts/op721-nft.md +785 -0
- package/docs/contracts/reentrancy-guard.md +787 -0
- package/docs/core-concepts/blockchain-environment.md +724 -0
- package/docs/core-concepts/decorators.md +466 -0
- package/docs/core-concepts/events.md +652 -0
- package/docs/core-concepts/pointers.md +391 -0
- package/docs/core-concepts/security.md +473 -0
- package/docs/core-concepts/storage-system.md +969 -0
- package/docs/examples/basic-token.md +745 -0
- package/docs/examples/nft-with-reservations.md +1440 -0
- package/docs/examples/oracle-integration.md +1212 -0
- package/docs/examples/stablecoin.md +1180 -0
- package/docs/getting-started/first-contract.md +575 -0
- package/docs/getting-started/installation.md +384 -0
- package/docs/getting-started/project-structure.md +630 -0
- package/docs/storage/memory-maps.md +764 -0
- package/docs/storage/stored-arrays.md +778 -0
- package/docs/storage/stored-maps.md +758 -0
- package/docs/storage/stored-primitives.md +655 -0
- package/docs/types/address.md +773 -0
- package/docs/types/bytes-writer-reader.md +938 -0
- package/docs/types/calldata.md +744 -0
- package/docs/types/safe-math.md +446 -0
- package/package.json +51 -26
- package/runtime/memory/MapOfMap.ts +1 -0
- package/LICENSE.md +0 -21
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
# Storage API Reference
|
|
2
|
+
|
|
3
|
+
Storage classes provide persistent state management for OPNet smart contracts.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import {
|
|
9
|
+
StoredU256,
|
|
10
|
+
StoredU64,
|
|
11
|
+
StoredU32,
|
|
12
|
+
StoredBoolean,
|
|
13
|
+
StoredString,
|
|
14
|
+
StoredAddress,
|
|
15
|
+
StoredU256Array,
|
|
16
|
+
StoredAddressArray,
|
|
17
|
+
AddressMemoryMap,
|
|
18
|
+
StoredMapU256,
|
|
19
|
+
Blockchain,
|
|
20
|
+
EMPTY_POINTER,
|
|
21
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## CRITICAL: Map Implementation Warning
|
|
25
|
+
|
|
26
|
+
> **DO NOT USE AssemblyScript's Built-in Map**
|
|
27
|
+
>
|
|
28
|
+
> When creating custom map implementations or extending map functionality, you **MUST** use the Map class from `@btc-vision/btc-runtime/runtime`, NOT the built-in AssemblyScript Map.
|
|
29
|
+
>
|
|
30
|
+
> **Why the AssemblyScript Map is broken for blockchain:**
|
|
31
|
+
> - NOT optimized for blockchain storage patterns
|
|
32
|
+
> - Does NOT handle Uint8Array buffers as keys correctly
|
|
33
|
+
> - Does NOT work properly with Address key comparisons
|
|
34
|
+
> - Will cause silent data corruption or key collisions
|
|
35
|
+
>
|
|
36
|
+
> **CORRECT:**
|
|
37
|
+
> ```typescript
|
|
38
|
+
> import { Map } from '@btc-vision/btc-runtime/runtime';
|
|
39
|
+
>
|
|
40
|
+
> export class MyCustomMap<V> extends Map<Address, V> {
|
|
41
|
+
> // Your implementation
|
|
42
|
+
> }
|
|
43
|
+
> ```
|
|
44
|
+
>
|
|
45
|
+
> **WRONG:**
|
|
46
|
+
> ```typescript
|
|
47
|
+
> // DO NOT DO THIS - will break!
|
|
48
|
+
> const map = new Map<Uint8Array, u256>(); // AssemblyScript Map
|
|
49
|
+
> ```
|
|
50
|
+
>
|
|
51
|
+
> The btc-runtime Map is specifically designed to:
|
|
52
|
+
> - Handle Address and Uint8Array key comparisons correctly
|
|
53
|
+
> - Optimize for blockchain storage access patterns
|
|
54
|
+
> - Support proper serialization for persistent storage
|
|
55
|
+
> - Prevent key collisions with custom equality logic
|
|
56
|
+
|
|
57
|
+
## Storage Type Hierarchy
|
|
58
|
+
|
|
59
|
+
The following diagram shows the complete hierarchy of storage types available in the runtime:
|
|
60
|
+
|
|
61
|
+
```mermaid
|
|
62
|
+
graph LR
|
|
63
|
+
A[Storage Types]
|
|
64
|
+
|
|
65
|
+
subgraph "Primitive Types"
|
|
66
|
+
B1[StoredU256<br/>StoredU64<br/>StoredU32]
|
|
67
|
+
B2[StoredBoolean<br/>StoredString<br/>StoredAddress]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
subgraph "Array Types"
|
|
71
|
+
C1[StoredU256Array<br/>StoredU64Array]
|
|
72
|
+
C2[StoredU32Array<br/>StoredAddressArray]
|
|
73
|
+
C3[StoredBooleanArray]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
subgraph "Map Types"
|
|
77
|
+
D1[AddressMemoryMap<br/>Address -> u256]
|
|
78
|
+
D2[StoredMapU256<br/>u256 -> u256]
|
|
79
|
+
D3[MapOfMap<br/>Nested maps]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
subgraph "Backend Storage"
|
|
83
|
+
E1[encodePointer<br/>Generate hash]
|
|
84
|
+
E2[getStorageAt<br/>Read value]
|
|
85
|
+
E3[setStorageAt<br/>Write value]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
A --> B1 & B2
|
|
89
|
+
A --> C1 & C2 & C3
|
|
90
|
+
A --> D1 & D2 & D3
|
|
91
|
+
|
|
92
|
+
B1 & B2 --> E1
|
|
93
|
+
C1 & C2 & C3 --> E1
|
|
94
|
+
D1 & D2 & D3 --> E1
|
|
95
|
+
|
|
96
|
+
E1 --> E2 & E3
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```mermaid
|
|
100
|
+
classDiagram
|
|
101
|
+
class StoredU256 {
|
|
102
|
+
-pointer: u16
|
|
103
|
+
-subPointer: Uint8Array
|
|
104
|
+
+get value() u256
|
|
105
|
+
+set value(v: u256)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class StoredString {
|
|
109
|
+
-pointer: u16
|
|
110
|
+
-index: u64
|
|
111
|
+
+get value() string
|
|
112
|
+
+set value(v: string)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
class AddressMemoryMap {
|
|
116
|
+
-pointer: u16
|
|
117
|
+
+get(key: Address) u256
|
|
118
|
+
+set(key: Address, value: u256)
|
|
119
|
+
+has(key: Address) bool
|
|
120
|
+
+delete(key: Address) bool
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
class StoredU256Array {
|
|
124
|
+
-pointer: u16
|
|
125
|
+
+getLength() u32
|
|
126
|
+
+push(value: u256) u32
|
|
127
|
+
+shift() u256
|
|
128
|
+
+get(index: u32) u256
|
|
129
|
+
+set(index: u32, value: u256)
|
|
130
|
+
+save()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class MapOfMap {
|
|
134
|
+
-pointer: u16
|
|
135
|
+
+get(key: Address) Nested~u256~
|
|
136
|
+
+set(key: Address, value: Nested~u256~)
|
|
137
|
+
+has(key: Address) bool
|
|
138
|
+
+delete(key: Address) bool
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
class Blockchain {
|
|
142
|
+
+getStorageAt(hash: Uint8Array) Uint8Array
|
|
143
|
+
+setStorageAt(hash: Uint8Array, value: Uint8Array)
|
|
144
|
+
+encodePointer(pointer: u16, subPointer: Uint8Array) Uint8Array
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
StoredU256 ..> Blockchain
|
|
148
|
+
StoredString ..> Blockchain
|
|
149
|
+
AddressMemoryMap ..> Blockchain
|
|
150
|
+
StoredU256Array ..> Blockchain
|
|
151
|
+
MapOfMap ..> Blockchain
|
|
152
|
+
|
|
153
|
+
note for Blockchain "All storage types use\nBlockchain as backend"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Primitive Storage
|
|
157
|
+
|
|
158
|
+
### StoredU256
|
|
159
|
+
|
|
160
|
+
Stores a 256-bit unsigned integer.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
class StoredU256 {
|
|
164
|
+
constructor(pointer: u16, subPointer: Uint8Array)
|
|
165
|
+
public get value(): u256
|
|
166
|
+
public set value(v: u256)
|
|
167
|
+
public get toBytes(): Uint8Array
|
|
168
|
+
public toString(): string
|
|
169
|
+
public set(value: u256): this
|
|
170
|
+
public add(value: u256): this // operator +
|
|
171
|
+
public sub(value: u256): this // operator -
|
|
172
|
+
public mul(value: u256): this // operator *
|
|
173
|
+
public addNoCommit(value: u256): this
|
|
174
|
+
public subNoCommit(value: u256): this
|
|
175
|
+
public commit(): this
|
|
176
|
+
public toUint8Array(): Uint8Array
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
private balancePointer: u16 = Blockchain.nextPointer;
|
|
182
|
+
private _balance: StoredU256 = new StoredU256(this.balancePointer, EMPTY_POINTER);
|
|
183
|
+
|
|
184
|
+
// Usage
|
|
185
|
+
this._balance.value = u256.fromU64(1000);
|
|
186
|
+
const balance = this._balance.value;
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The following sequence diagram shows how storage read and write operations work:
|
|
190
|
+
|
|
191
|
+
```mermaid
|
|
192
|
+
sequenceDiagram
|
|
193
|
+
participant Contract
|
|
194
|
+
participant Storage as Storage Object
|
|
195
|
+
participant Encode as encodePointer
|
|
196
|
+
participant BC as Blockchain
|
|
197
|
+
|
|
198
|
+
Note over Contract,BC: Write Operation
|
|
199
|
+
|
|
200
|
+
Contract->>Contract: private balance: StoredU256
|
|
201
|
+
Contract->>Contract: balance.value = u256.fromU64(1000)
|
|
202
|
+
|
|
203
|
+
Storage->>Encode: encodePointer(pointer, subPointer)
|
|
204
|
+
Encode->>Encode: SHA-256(pointer + subPointer)
|
|
205
|
+
Encode->>Storage: Return 32-byte hash
|
|
206
|
+
|
|
207
|
+
Storage->>Storage: value.toUint8Array(true)
|
|
208
|
+
Storage->>BC: setStorageAt(hash, bytes)
|
|
209
|
+
BC->>BC: Write to persistent storage
|
|
210
|
+
|
|
211
|
+
Note over Contract,BC: Read Operation
|
|
212
|
+
|
|
213
|
+
Contract->>Contract: const amount = balance.value
|
|
214
|
+
|
|
215
|
+
Storage->>Encode: encodePointer(pointer, subPointer)
|
|
216
|
+
Encode->>Storage: Return 32-byte hash
|
|
217
|
+
|
|
218
|
+
Storage->>BC: getStorageAt(hash)
|
|
219
|
+
BC->>Storage: Return 32-byte value
|
|
220
|
+
Storage->>Storage: u256.fromUint8ArrayBE(bytes)
|
|
221
|
+
Storage->>Contract: Return u256 value
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### StoredU64
|
|
225
|
+
|
|
226
|
+
Stores up to four 64-bit unsigned integers within a single u256 storage slot.
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
class StoredU64 {
|
|
230
|
+
constructor(pointer: u16, subPointer: Uint8Array)
|
|
231
|
+
public get(index: u8): u64 // index 0-3
|
|
232
|
+
public set(index: u8, value: u64): void
|
|
233
|
+
public save(): void
|
|
234
|
+
public getAll(): u64[]
|
|
235
|
+
public setMultiple(values: u64[]): void
|
|
236
|
+
public reset(): void
|
|
237
|
+
public toString(): string
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
private timestampPointer: u16 = Blockchain.nextPointer;
|
|
243
|
+
private _timestamps: StoredU64 = new StoredU64(this.timestampPointer, EMPTY_POINTER);
|
|
244
|
+
|
|
245
|
+
// Usage - stores up to 4 u64 values in one storage slot
|
|
246
|
+
this._timestamps.set(0, Blockchain.block.medianTime); // First u64
|
|
247
|
+
this._timestamps.set(1, someOtherTimestamp); // Second u64
|
|
248
|
+
this._timestamps.save(); // Commit to storage
|
|
249
|
+
|
|
250
|
+
const firstTimestamp = this._timestamps.get(0);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### StoredU32
|
|
254
|
+
|
|
255
|
+
Stores up to eight 32-bit unsigned integers within a single u256 storage slot.
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
class StoredU32 {
|
|
259
|
+
constructor(pointer: u16, subPointer: Uint8Array)
|
|
260
|
+
public get(index: u8): u32 // index 0-7
|
|
261
|
+
public set(index: u8, value: u32): void
|
|
262
|
+
public save(): void
|
|
263
|
+
public getAll(): u32[]
|
|
264
|
+
public setMultiple(values: u32[]): void
|
|
265
|
+
public reset(): void
|
|
266
|
+
public toString(): string
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
private configPointer: u16 = Blockchain.nextPointer;
|
|
272
|
+
private _config: StoredU32 = new StoredU32(this.configPointer, EMPTY_POINTER);
|
|
273
|
+
|
|
274
|
+
// Usage - stores up to 8 u32 values in one storage slot
|
|
275
|
+
this._config.set(0, 100); // First u32
|
|
276
|
+
this._config.set(1, 200); // Second u32
|
|
277
|
+
this._config.save(); // Commit to storage
|
|
278
|
+
|
|
279
|
+
const firstValue = this._config.get(0);
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### StoredBoolean
|
|
283
|
+
|
|
284
|
+
Stores a boolean value.
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
class StoredBoolean {
|
|
288
|
+
constructor(pointer: u16, defaultValue: bool)
|
|
289
|
+
public get value(): bool
|
|
290
|
+
public set value(v: bool)
|
|
291
|
+
public toUint8Array(): Uint8Array
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
private pausedPointer: u16 = Blockchain.nextPointer;
|
|
297
|
+
private _paused: StoredBoolean = new StoredBoolean(this.pausedPointer, false);
|
|
298
|
+
|
|
299
|
+
// Usage
|
|
300
|
+
this._paused.value = true;
|
|
301
|
+
if (this._paused.value) {
|
|
302
|
+
throw new Revert('Contract is paused');
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### StoredString
|
|
307
|
+
|
|
308
|
+
Stores a string value.
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
class StoredString {
|
|
312
|
+
constructor(pointer: u16, index: u64 = 0)
|
|
313
|
+
public get value(): string
|
|
314
|
+
public set value(v: string)
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
private namePointer: u16 = Blockchain.nextPointer;
|
|
320
|
+
private _name: StoredString = new StoredString(this.namePointer, 0);
|
|
321
|
+
|
|
322
|
+
// Usage
|
|
323
|
+
this._name.value = 'My Token';
|
|
324
|
+
const name = this._name.value;
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### StoredAddress
|
|
328
|
+
|
|
329
|
+
Stores an Address value. Default value is Address.zero().
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
class StoredAddress {
|
|
333
|
+
constructor(pointer: u16)
|
|
334
|
+
public get value(): Address
|
|
335
|
+
public set value(v: Address)
|
|
336
|
+
public isDead(): bool // Note: checks if address equals Address.zero(), not ExtendedAddress.dead()
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
private ownerPointer: u16 = Blockchain.nextPointer;
|
|
342
|
+
private _owner: StoredAddress = new StoredAddress(this.ownerPointer);
|
|
343
|
+
|
|
344
|
+
// Usage
|
|
345
|
+
this._owner.value = Blockchain.tx.origin;
|
|
346
|
+
const owner = this._owner.value;
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Array Storage
|
|
350
|
+
|
|
351
|
+
### StoredU256Array
|
|
352
|
+
|
|
353
|
+
Dynamic array of u256 values. Elements are packed into 32-byte storage slots.
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
class StoredU256Array {
|
|
357
|
+
constructor(pointer: u16, subPointer: Uint8Array, maxLength: u32 = DEFAULT_MAX_LENGTH)
|
|
358
|
+
public getLength(): u32
|
|
359
|
+
public push(value: u256, isPhysical?: bool): u32
|
|
360
|
+
public deleteLast(): void
|
|
361
|
+
public delete(index: u32): void
|
|
362
|
+
public shift(): u256
|
|
363
|
+
public get(index: u32): u256
|
|
364
|
+
public set(index: u32, value: u256): void
|
|
365
|
+
public getAll(startIndex: u32, count: u32): u256[]
|
|
366
|
+
public setMultiple(startIndex: u32, values: u256[]): void
|
|
367
|
+
public save(): void
|
|
368
|
+
public reset(): void
|
|
369
|
+
public deleteAll(): void
|
|
370
|
+
public startingIndex(): u32
|
|
371
|
+
public setStartingIndex(index: u32): void
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
private tokenIdsPointer: u16 = Blockchain.nextPointer;
|
|
377
|
+
private tokenIds: StoredU256Array = new StoredU256Array(this.tokenIdsPointer, EMPTY_POINTER);
|
|
378
|
+
|
|
379
|
+
// Usage
|
|
380
|
+
this.tokenIds.push(u256.fromU64(1));
|
|
381
|
+
this.tokenIds.push(u256.fromU64(2));
|
|
382
|
+
this.tokenIds.save(); // Commit changes to storage
|
|
383
|
+
|
|
384
|
+
const first = this.tokenIds.get(0); // u256.fromU64(1)
|
|
385
|
+
const len = this.tokenIds.getLength(); // 2
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
The following diagram shows the array operation flow:
|
|
389
|
+
|
|
390
|
+
```mermaid
|
|
391
|
+
flowchart LR
|
|
392
|
+
A[StoredU256Array] --> B{Operation<br/>Type}
|
|
393
|
+
|
|
394
|
+
subgraph "push Operation"
|
|
395
|
+
B -->|push| C[Get current length]
|
|
396
|
+
C --> D[Encode pointer<br/>with index]
|
|
397
|
+
D --> E[Write value at index]
|
|
398
|
+
E --> F[Increment length]
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
subgraph "get Operation"
|
|
402
|
+
B -->|get| G[Validate<br/>index < length]
|
|
403
|
+
G --> H[Encode pointer<br/>with index]
|
|
404
|
+
H --> I[Read value<br/>from storage]
|
|
405
|
+
I --> J[Return u256]
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
subgraph "shift Operation"
|
|
409
|
+
B -->|shift| K[Validate<br/>length > 0]
|
|
410
|
+
K --> L[Read first element]
|
|
411
|
+
L --> M[Decrement length]
|
|
412
|
+
M --> N[Return value]
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
subgraph "set Operation"
|
|
416
|
+
B -->|set| O[Validate<br/>index < length]
|
|
417
|
+
O --> P[Encode pointer<br/>with index]
|
|
418
|
+
P --> Q[Write new value]
|
|
419
|
+
end
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### StoredAddressArray
|
|
423
|
+
|
|
424
|
+
Dynamic array of Address values. Each address takes one 32-byte storage slot.
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
class StoredAddressArray {
|
|
428
|
+
constructor(pointer: u16, subPointer: Uint8Array, maxLength: u32 = DEFAULT_MAX_LENGTH)
|
|
429
|
+
public getLength(): u32
|
|
430
|
+
public push(value: Address, isPhysical?: bool): u32
|
|
431
|
+
public deleteLast(): void
|
|
432
|
+
public delete(index: u32): void
|
|
433
|
+
public shift(): Address
|
|
434
|
+
public get(index: u32): Address
|
|
435
|
+
public set(index: u32, value: Address): void
|
|
436
|
+
public getAll(startIndex: u32, count: u32): Address[]
|
|
437
|
+
public setMultiple(startIndex: u32, values: Address[]): void
|
|
438
|
+
public save(): void
|
|
439
|
+
public reset(): void
|
|
440
|
+
public deleteAll(): void
|
|
441
|
+
public startingIndex(): u32
|
|
442
|
+
public setStartingIndex(index: u32): void
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
private oraclesPointer: u16 = Blockchain.nextPointer;
|
|
448
|
+
private oracles: StoredAddressArray = new StoredAddressArray(this.oraclesPointer, EMPTY_POINTER);
|
|
449
|
+
|
|
450
|
+
// Add oracle
|
|
451
|
+
this.oracles.push(oracleAddress);
|
|
452
|
+
this.oracles.save(); // Commit changes
|
|
453
|
+
|
|
454
|
+
// Iterate
|
|
455
|
+
for (let i: u32 = 0; i < this.oracles.getLength(); i++) {
|
|
456
|
+
const oracle = this.oracles.get(i);
|
|
457
|
+
// Process oracle
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Other Array Types
|
|
462
|
+
|
|
463
|
+
All array types share the same base API as `StoredU256Array` (extending `StoredPackedArray<T>`) with their respective element types:
|
|
464
|
+
|
|
465
|
+
- `StoredU128Array` - 2 u128 values per 32-byte slot
|
|
466
|
+
- `StoredU64Array` - 4 u64 values per 32-byte slot
|
|
467
|
+
- `StoredU32Array` - 8 u32 values per 32-byte slot
|
|
468
|
+
- `StoredU16Array` - 16 u16 values per 32-byte slot
|
|
469
|
+
- `StoredU8Array` - 32 u8 values per 32-byte slot
|
|
470
|
+
- `StoredBooleanArray` - 256 boolean values per 32-byte slot (bit-packed)
|
|
471
|
+
|
|
472
|
+
> **Note:** These are array types only. There are no standalone `StoredU128`, `StoredU16`, or `StoredU8` primitive classes. For storing single small values, use `StoredU64` (which packs 4 u64 values) or `StoredU32` (which packs 8 u32 values) in a single storage slot.
|
|
473
|
+
|
|
474
|
+
## Map Storage
|
|
475
|
+
|
|
476
|
+
### AddressMemoryMap
|
|
477
|
+
|
|
478
|
+
Maps addresses to u256 values. Always returns u256.Zero for unset addresses.
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
class AddressMemoryMap {
|
|
482
|
+
constructor(pointer: u16)
|
|
483
|
+
public get(key: Address): u256
|
|
484
|
+
public set(key: Address, value: u256): this
|
|
485
|
+
public getAsUint8Array(key: Address): Uint8Array
|
|
486
|
+
public setAsUint8Array(key: Address, value: Uint8Array): this
|
|
487
|
+
public has(key: Address): bool
|
|
488
|
+
public delete(key: Address): bool
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
The following diagram shows how map keys are converted to storage hashes:
|
|
493
|
+
|
|
494
|
+
```mermaid
|
|
495
|
+
flowchart LR
|
|
496
|
+
subgraph "Key to Hash"
|
|
497
|
+
A[AddressMemoryMap] --> B[Address Key]
|
|
498
|
+
B --> C[encodePointer<br/>pointer, address.toBytes]
|
|
499
|
+
C --> D[32-byte storage hash]
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
subgraph "Operations"
|
|
503
|
+
D --> E{get or set?}
|
|
504
|
+
E -->|get| F[Blockchain.getStorageAt<br/>hash]
|
|
505
|
+
F --> G[Convert to u256]
|
|
506
|
+
G --> H[Return value<br/>or u256.Zero]
|
|
507
|
+
E -->|set| I[value.toUint8Array]
|
|
508
|
+
I --> J[Blockchain.setStorageAt<br/>hash, bytes]
|
|
509
|
+
end
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
#### Usage Example
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
// mapping(address => uint256)
|
|
516
|
+
private balancesPointer: u16 = Blockchain.nextPointer;
|
|
517
|
+
private balances: AddressMemoryMap;
|
|
518
|
+
|
|
519
|
+
constructor() {
|
|
520
|
+
super();
|
|
521
|
+
this.balances = new AddressMemoryMap(this.balancesPointer);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Usage
|
|
525
|
+
const balance = this.balances.get(userAddress); // Returns u256
|
|
526
|
+
this.balances.set(userAddress, u256.fromU64(1000));
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
The following sequence diagram shows the complete balance mapping flow:
|
|
530
|
+
|
|
531
|
+
```mermaid
|
|
532
|
+
sequenceDiagram
|
|
533
|
+
participant Contract
|
|
534
|
+
participant Map as AddressMemoryMap
|
|
535
|
+
participant BC as Blockchain
|
|
536
|
+
|
|
537
|
+
Note over Contract,BC: Balance Mapping Example
|
|
538
|
+
|
|
539
|
+
Contract->>Contract: balances = new AddressMemoryMap(pointer)
|
|
540
|
+
|
|
541
|
+
Note over Contract,BC: Set Balance
|
|
542
|
+
|
|
543
|
+
Contract->>Map: balances.set(userAddress, u256.fromU64(1000))
|
|
544
|
+
Map->>Map: hash = encodePointer(pointer, userAddress.toBytes())
|
|
545
|
+
Map->>BC: setStorageAt(hash, 1000.toUint8Array())
|
|
546
|
+
BC->>Map: Storage updated
|
|
547
|
+
|
|
548
|
+
Note over Contract,BC: Get Balance
|
|
549
|
+
|
|
550
|
+
Contract->>Map: balances.get(userAddress)
|
|
551
|
+
Map->>Map: hash = encodePointer(pointer, userAddress.toBytes())
|
|
552
|
+
Map->>BC: getStorageAt(hash)
|
|
553
|
+
BC->>Map: Return bytes
|
|
554
|
+
Map->>Map: u256.fromUint8ArrayBE(bytes)
|
|
555
|
+
Map->>Contract: Return u256.fromU64(1000)
|
|
556
|
+
|
|
557
|
+
Note over Contract,BC: Get Non-Existent Balance
|
|
558
|
+
|
|
559
|
+
Contract->>Map: balances.get(unknownAddress)
|
|
560
|
+
Map->>BC: getStorageAt(hash)
|
|
561
|
+
BC->>Map: Return zeros
|
|
562
|
+
Map->>Contract: Return u256.Zero
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### StoredMapU256
|
|
566
|
+
|
|
567
|
+
Maps u256 keys to u256 values.
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
class StoredMapU256 {
|
|
571
|
+
constructor(pointer: u16, subPointer: Uint8Array = new Uint8Array(30))
|
|
572
|
+
public get(key: u256): u256
|
|
573
|
+
public set(key: u256, value: u256): void
|
|
574
|
+
public delete(key: u256): void // Sets value to zero
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Note: `StoredMapU256` does not have a `has()` method. To check if a key exists, compare the returned value with `u256.Zero`.
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
private dataPointer: u16 = Blockchain.nextPointer;
|
|
582
|
+
private data: StoredMapU256 = new StoredMapU256(this.dataPointer);
|
|
583
|
+
|
|
584
|
+
// Usage
|
|
585
|
+
const key = u256.fromU64(123);
|
|
586
|
+
this.data.set(key, u256.fromU64(456));
|
|
587
|
+
const value = this.data.get(key);
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Nested Maps (MapOfMap)
|
|
591
|
+
|
|
592
|
+
For allowances pattern (owner => spender => amount):
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
class MapOfMap<T> {
|
|
596
|
+
constructor(pointer: u16)
|
|
597
|
+
public get(key: Address): Nested<T>
|
|
598
|
+
public set(key: Address, value: Nested<T>): this
|
|
599
|
+
public has(key: Address): bool
|
|
600
|
+
public delete(key: Address): bool
|
|
601
|
+
public clear(): void
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
class Nested<T> {
|
|
605
|
+
constructor(parent: Uint8Array, pointer: u16)
|
|
606
|
+
public get(key: Uint8Array): T
|
|
607
|
+
public set(key: Uint8Array, value: T): this
|
|
608
|
+
public has(key: Uint8Array): bool
|
|
609
|
+
}
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
import { MapOfMap, Nested } from '@btc-vision/btc-runtime/runtime';
|
|
614
|
+
|
|
615
|
+
// mapping(address => mapping(address => uint256))
|
|
616
|
+
private allowancesPointer: u16 = Blockchain.nextPointer;
|
|
617
|
+
private allowances: MapOfMap<u256>;
|
|
618
|
+
|
|
619
|
+
constructor() {
|
|
620
|
+
super();
|
|
621
|
+
this.allowances = new MapOfMap<u256>(this.allowancesPointer);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Get nested value - two-step process
|
|
625
|
+
protected getAllowance(owner: Address, spender: Address): u256 {
|
|
626
|
+
const ownerMap = this.allowances.get(owner); // Returns Nested<u256>
|
|
627
|
+
return ownerMap.get(spender); // Returns u256
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Set nested value - get, modify, commit back
|
|
631
|
+
protected setAllowance(owner: Address, spender: Address, amount: u256): void {
|
|
632
|
+
const ownerMap = this.allowances.get(owner); // Get the nested map
|
|
633
|
+
ownerMap.set(spender, amount); // Modify it
|
|
634
|
+
this.allowances.set(owner, ownerMap); // Commit back
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
> **Important:** `MapOfMap.get(key)` returns a `Nested<T>` object, not the final value. You must call `.get()` on the nested object to retrieve the actual value.
|
|
639
|
+
|
|
640
|
+
The following diagram shows the two-level nested storage pattern:
|
|
641
|
+
|
|
642
|
+
```mermaid
|
|
643
|
+
flowchart LR
|
|
644
|
+
subgraph "Read Path - Two-Level Mapping"
|
|
645
|
+
A[MapOfMap allowances] --> B[First Level:<br/>Owner Address]
|
|
646
|
+
B --> C[allowances.get owner]
|
|
647
|
+
C --> D[Returns Nested<br/>u256 Map]
|
|
648
|
+
D --> E[Second Level:<br/>Spender Address]
|
|
649
|
+
E --> F[nested.get spender]
|
|
650
|
+
F --> G[Returns u256<br/>allowance]
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
subgraph "Write Path - Set Allowance"
|
|
654
|
+
H[Set Allowance] --> I[Get owner's<br/>nested map]
|
|
655
|
+
I --> J[nested.set<br/>spender, amount]
|
|
656
|
+
J --> K[Commit back<br/>to MapOfMap]
|
|
657
|
+
K --> L[allowances.set<br/>owner, nested]
|
|
658
|
+
end
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
The following sequence diagram shows the nested allowance operations:
|
|
662
|
+
|
|
663
|
+
```mermaid
|
|
664
|
+
sequenceDiagram
|
|
665
|
+
participant Contract
|
|
666
|
+
participant MapOfMap as allowances MapOfMap
|
|
667
|
+
participant Nested as Nested Map
|
|
668
|
+
participant BC as Blockchain
|
|
669
|
+
|
|
670
|
+
Note over Contract,BC: Get Nested Allowance
|
|
671
|
+
|
|
672
|
+
Contract->>MapOfMap: allowances.get(owner)
|
|
673
|
+
MapOfMap->>MapOfMap: Compute owner's map hash
|
|
674
|
+
MapOfMap->>Contract: Return Nested~u256~ map
|
|
675
|
+
|
|
676
|
+
Contract->>Nested: nested.get(spender)
|
|
677
|
+
Nested->>Nested: Compute spender hash within owner's space
|
|
678
|
+
Nested->>BC: getStorageAt(combined_hash)
|
|
679
|
+
BC->>Nested: Return allowance bytes
|
|
680
|
+
Nested->>Contract: Return u256 allowance
|
|
681
|
+
|
|
682
|
+
Note over Contract,BC: Set Nested Allowance
|
|
683
|
+
|
|
684
|
+
Contract->>MapOfMap: allowances.get(owner)
|
|
685
|
+
MapOfMap->>Contract: Return Nested map
|
|
686
|
+
|
|
687
|
+
Contract->>Nested: nested.set(spender, new_amount)
|
|
688
|
+
Nested->>BC: setStorageAt(combined_hash, new_amount)
|
|
689
|
+
|
|
690
|
+
Contract->>MapOfMap: allowances.set(owner, nested)
|
|
691
|
+
Note over Contract: Commit nested map changes
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
## Storage Patterns
|
|
695
|
+
|
|
696
|
+
### Lazy Initialization
|
|
697
|
+
|
|
698
|
+
```typescript
|
|
699
|
+
private _data: StoredU256 | null = null;
|
|
700
|
+
private dataPointer: u16 = Blockchain.nextPointer;
|
|
701
|
+
|
|
702
|
+
private get data(): StoredU256 {
|
|
703
|
+
if (!this._data) {
|
|
704
|
+
this._data = new StoredU256(this.dataPointer, EMPTY_POINTER);
|
|
705
|
+
}
|
|
706
|
+
return this._data;
|
|
707
|
+
}
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### Computed Storage Keys
|
|
711
|
+
|
|
712
|
+
```typescript
|
|
713
|
+
import { encodePointer } from '@btc-vision/btc-runtime/runtime';
|
|
714
|
+
|
|
715
|
+
private storagePointer: u16 = Blockchain.nextPointer;
|
|
716
|
+
|
|
717
|
+
private getKey(addr: Address, slot: u256): u256 {
|
|
718
|
+
const combined = new Uint8Array(64);
|
|
719
|
+
combined.set(addr.toBytes(), 0);
|
|
720
|
+
combined.set(slot.toUint8Array(), 32);
|
|
721
|
+
return u256.fromBytes(Blockchain.sha256(combined));
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
public getValue(addr: Address, slot: u256): u256 {
|
|
725
|
+
const key = this.getKey(addr, slot);
|
|
726
|
+
const pointerHash = encodePointer(this.storagePointer, key.toUint8Array(true));
|
|
727
|
+
const stored = Blockchain.getStorageAt(pointerHash);
|
|
728
|
+
return u256.fromUint8ArrayBE(stored);
|
|
729
|
+
}
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
### Counter Pattern
|
|
733
|
+
|
|
734
|
+
```typescript
|
|
735
|
+
private counterPointer: u16 = Blockchain.nextPointer;
|
|
736
|
+
private _counter: StoredU256 = new StoredU256(this.counterPointer, EMPTY_POINTER);
|
|
737
|
+
|
|
738
|
+
public getNextId(): u256 {
|
|
739
|
+
const current = this._counter.value;
|
|
740
|
+
this._counter.value = SafeMath.add(current, u256.One);
|
|
741
|
+
return current;
|
|
742
|
+
}
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
## Low-Level Storage
|
|
746
|
+
|
|
747
|
+
Direct storage access via Blockchain:
|
|
748
|
+
|
|
749
|
+
```typescript
|
|
750
|
+
import { encodePointer } from '@btc-vision/btc-runtime/runtime';
|
|
751
|
+
|
|
752
|
+
// Generate storage key hash
|
|
753
|
+
public encodePointer(pointer: u16, subPointer: Uint8Array): Uint8Array
|
|
754
|
+
|
|
755
|
+
// Write to storage
|
|
756
|
+
public setStorageAt(pointerHash: Uint8Array, value: Uint8Array): void
|
|
757
|
+
|
|
758
|
+
// Read from storage
|
|
759
|
+
public getStorageAt(pointerHash: Uint8Array): Uint8Array
|
|
760
|
+
|
|
761
|
+
// Check existence
|
|
762
|
+
public hasStorageAt(pointerHash: Uint8Array): bool
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
```typescript
|
|
766
|
+
// Direct storage example
|
|
767
|
+
const pointer: u16 = Blockchain.nextPointer;
|
|
768
|
+
const subPointer = u256.fromU64(1).toUint8Array(true);
|
|
769
|
+
|
|
770
|
+
// Create storage key
|
|
771
|
+
const pointerHash = encodePointer(pointer, subPointer);
|
|
772
|
+
|
|
773
|
+
// Write value
|
|
774
|
+
Blockchain.setStorageAt(pointerHash, u256.fromU64(100).toUint8Array(true));
|
|
775
|
+
|
|
776
|
+
// Read value
|
|
777
|
+
const stored = Blockchain.getStorageAt(pointerHash);
|
|
778
|
+
const value = u256.fromUint8ArrayBE(stored);
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
## Best Practices
|
|
782
|
+
|
|
783
|
+
### 1. Declare Pointers First
|
|
784
|
+
|
|
785
|
+
```typescript
|
|
786
|
+
class MyContract extends OP_NET {
|
|
787
|
+
// Declare all pointers before any stored values
|
|
788
|
+
private ptr1: u16 = Blockchain.nextPointer;
|
|
789
|
+
private ptr2: u16 = Blockchain.nextPointer;
|
|
790
|
+
private ptr3: u16 = Blockchain.nextPointer;
|
|
791
|
+
|
|
792
|
+
// Then declare stored values
|
|
793
|
+
private _value1: StoredU256;
|
|
794
|
+
private _value2: StoredBoolean;
|
|
795
|
+
|
|
796
|
+
public constructor() {
|
|
797
|
+
super();
|
|
798
|
+
this._value1 = new StoredU256(this.ptr1, EMPTY_POINTER);
|
|
799
|
+
this._value2 = new StoredBoolean(this.ptr2, false);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
### 2. Use Appropriate Types
|
|
805
|
+
|
|
806
|
+
```typescript
|
|
807
|
+
// Good - use smallest type that fits
|
|
808
|
+
private _count: StoredU32; // If count < 4 billion
|
|
809
|
+
|
|
810
|
+
// Less efficient
|
|
811
|
+
private _count: StoredU256; // Uses more storage
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### 3. Batch Updates
|
|
815
|
+
|
|
816
|
+
```typescript
|
|
817
|
+
// Multiple related updates in one call
|
|
818
|
+
public updateBoth(a: u256, b: u256): void {
|
|
819
|
+
this._valueA.value = a;
|
|
820
|
+
this._valueB.value = b;
|
|
821
|
+
}
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
## Solidity Comparison
|
|
825
|
+
|
|
826
|
+
| Solidity | OPNet Storage |
|
|
827
|
+
|----------|---------------|
|
|
828
|
+
| `uint256 public value` | `StoredU256` |
|
|
829
|
+
| `mapping(address => uint256)` | `AddressMemoryMap` |
|
|
830
|
+
| `mapping(address => mapping(address => uint256))` | `MapOfMap<u256>` |
|
|
831
|
+
| `uint256[] public array` | `StoredU256Array` |
|
|
832
|
+
| `bool public paused` | `StoredBoolean` |
|
|
833
|
+
| `string public name` | `StoredString` |
|
|
834
|
+
| `address public owner` | `StoredAddress` |
|
|
835
|
+
|
|
836
|
+
---
|
|
837
|
+
|
|
838
|
+
**Navigation:**
|
|
839
|
+
- Previous: [SafeMath API](./safe-math.md)
|
|
840
|
+
- Next: [Events API](./events.md)
|