@btc-vision/btc-runtime 1.10.8 → 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.
Files changed (44) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +258 -137
  3. package/SECURITY.md +226 -0
  4. package/docs/README.md +614 -0
  5. package/docs/advanced/bitcoin-scripts.md +939 -0
  6. package/docs/advanced/cross-contract-calls.md +579 -0
  7. package/docs/advanced/plugins.md +1006 -0
  8. package/docs/advanced/quantum-resistance.md +660 -0
  9. package/docs/advanced/signature-verification.md +715 -0
  10. package/docs/api-reference/blockchain.md +729 -0
  11. package/docs/api-reference/events.md +642 -0
  12. package/docs/api-reference/op20.md +902 -0
  13. package/docs/api-reference/op721.md +819 -0
  14. package/docs/api-reference/safe-math.md +510 -0
  15. package/docs/api-reference/storage.md +840 -0
  16. package/docs/contracts/op-net-base.md +786 -0
  17. package/docs/contracts/op20-token.md +687 -0
  18. package/docs/contracts/op20s-signatures.md +614 -0
  19. package/docs/contracts/op721-nft.md +785 -0
  20. package/docs/contracts/reentrancy-guard.md +787 -0
  21. package/docs/core-concepts/blockchain-environment.md +724 -0
  22. package/docs/core-concepts/decorators.md +466 -0
  23. package/docs/core-concepts/events.md +652 -0
  24. package/docs/core-concepts/pointers.md +391 -0
  25. package/docs/core-concepts/security.md +473 -0
  26. package/docs/core-concepts/storage-system.md +969 -0
  27. package/docs/examples/basic-token.md +745 -0
  28. package/docs/examples/nft-with-reservations.md +1440 -0
  29. package/docs/examples/oracle-integration.md +1212 -0
  30. package/docs/examples/stablecoin.md +1180 -0
  31. package/docs/getting-started/first-contract.md +575 -0
  32. package/docs/getting-started/installation.md +384 -0
  33. package/docs/getting-started/project-structure.md +630 -0
  34. package/docs/storage/memory-maps.md +764 -0
  35. package/docs/storage/stored-arrays.md +778 -0
  36. package/docs/storage/stored-maps.md +758 -0
  37. package/docs/storage/stored-primitives.md +655 -0
  38. package/docs/types/address.md +773 -0
  39. package/docs/types/bytes-writer-reader.md +938 -0
  40. package/docs/types/calldata.md +744 -0
  41. package/docs/types/safe-math.md +446 -0
  42. package/package.json +52 -27
  43. package/runtime/memory/MapOfMap.ts +1 -0
  44. package/LICENSE.md +0 -21
@@ -0,0 +1,778 @@
1
+ # Stored Arrays
2
+
3
+ Stored arrays persist ordered collections of values on-chain. They support push, pop, get, set, and length operations with automatic bounds checking.
4
+
5
+ ## Overview
6
+
7
+ ```typescript
8
+ import {
9
+ StoredU256Array,
10
+ StoredU128Array,
11
+ StoredU64Array,
12
+ StoredAddressArray,
13
+ StoredBooleanArray,
14
+ Blockchain,
15
+ } from '@btc-vision/btc-runtime/runtime';
16
+ import { u256 } from '@btc-vision/as-bignum/assembly';
17
+
18
+ // Allocate storage pointer
19
+ private holdersPointer: u16 = Blockchain.nextPointer;
20
+
21
+ // Create stored array with subPointer
22
+ private holders: StoredAddressArray;
23
+
24
+ constructor() {
25
+ super();
26
+ this.holders = new StoredAddressArray(this.holdersPointer, EMPTY_POINTER);
27
+ }
28
+
29
+ // Operations
30
+ this.holders.push(newAddress);
31
+ const first = this.holders.get(0);
32
+ const length = this.holders.getLength();
33
+ this.holders.deleteLast(); // removes last element
34
+ this.holders.save(); // commit changes to storage
35
+ ```
36
+
37
+ ## Available Types
38
+
39
+ | Type | Element Type | Description |
40
+ |------|-------------|-------------|
41
+ | `StoredU256Array` | `u256` | Array of 256-bit unsigned |
42
+ | `StoredU128Array` | `u128` | Array of 128-bit unsigned |
43
+ | `StoredU64Array` | `u64` | Array of 64-bit unsigned |
44
+ | `StoredU32Array` | `u32` | Array of 32-bit unsigned |
45
+ | `StoredU16Array` | `u16` | Array of 16-bit unsigned |
46
+ | `StoredU8Array` | `u8` | Array of bytes |
47
+ | `StoredAddressArray` | `Address` | Array of addresses |
48
+ | `StoredBooleanArray` | `bool` | Array of booleans |
49
+
50
+ ## Storage Structure
51
+
52
+ Arrays use multiple storage slots with sequential subPointers for each element:
53
+
54
+ ```mermaid
55
+ flowchart LR
56
+ subgraph instance["StoredArray Instance"]
57
+ A["pointer: u16<br/>e.g., 0x0005"]
58
+ end
59
+
60
+ subgraph layout["Storage Layout"]
61
+ B["Length Slot<br/>pointer base"]
62
+ C["Element 0<br/>subPointer = 0"]
63
+ D["Element 1<br/>subPointer = 1"]
64
+ E["Element 2<br/>subPointer = 2"]
65
+ F["Element 3<br/>subPointer = 3"]
66
+ G["..."]
67
+ end
68
+
69
+ subgraph keys["Storage Keys (SHA256)"]
70
+ H["pointer + 0<br/>-> length: u64"]
71
+ I["pointer + 1<br/>-> element 0"]
72
+ J["pointer + 2<br/>-> element 1"]
73
+ K["pointer + 3<br/>-> element 2"]
74
+ end
75
+
76
+ A --> B
77
+ B --> H
78
+ C --> I
79
+ D --> J
80
+ E --> K
81
+
82
+ H --> L[("Blockchain Storage")]
83
+ I --> L
84
+ J --> L
85
+ K --> L
86
+ ```
87
+
88
+ ### Size Limits
89
+
90
+ Maximum array length: **4,294,967,294 elements** (u32.MAX_VALUE - 1), though practical limits depend on gas costs.
91
+
92
+ ```typescript
93
+ // Check before adding
94
+ if (this.holders.getLength() >= MAX_ALLOWED) {
95
+ throw new Revert('Array full');
96
+ }
97
+ this.holders.push(newHolder);
98
+ this.holders.save(); // Don't forget to save!
99
+ ```
100
+
101
+ ## Operations
102
+
103
+ ### Push
104
+
105
+ Add element to end. The push operation follows this flow:
106
+
107
+ ```mermaid
108
+ ---
109
+ config:
110
+ theme: dark
111
+ ---
112
+ flowchart LR
113
+ A["array.push(value)"] --> B["Read length"]
114
+ B --> C{"length < 65535?"}
115
+ C -->|"No"| D["Throw error"]
116
+ C -->|"Yes"| E["Calculate key"]
117
+ E --> F["setStorageAt()"]
118
+ F --> G["Increment length"]
119
+ G --> H["Save length"]
120
+ ```
121
+
122
+ ```typescript
123
+ // Add new element
124
+ this.holders.push(newHolder);
125
+ this.holders.save(); // Commit changes
126
+
127
+ // Length increases by 1
128
+ const newLength = this.holders.getLength();
129
+ ```
130
+
131
+ ### deleteLast / shift
132
+
133
+ Remove elements from the array. `deleteLast()` removes from the end, `shift()` removes from the beginning.
134
+
135
+ ```mermaid
136
+ ---
137
+ config:
138
+ theme: dark
139
+ ---
140
+ flowchart LR
141
+ A["array.deleteLast()"] --> B["Read length"]
142
+ B --> C{"length > 0?"}
143
+ C -->|"No"| D["Throw error"]
144
+ C -->|"Yes"| E["Zero last element"]
145
+ E --> F["Decrement length"]
146
+ F --> G["Mark as changed"]
147
+ ```
148
+
149
+ ```typescript
150
+ // Remove last element
151
+ this.holders.deleteLast();
152
+ this.holders.save(); // Commit changes
153
+
154
+ // Remove first element and return it
155
+ const removed: Address = this.holders.shift();
156
+ this.holders.save(); // Commit changes
157
+
158
+ // Reverts if array is empty
159
+ ```
160
+
161
+ ### Get
162
+
163
+ Read element at index:
164
+
165
+ ```typescript
166
+ // Get element at index
167
+ const holder: Address = this.holders.get(index);
168
+
169
+ // Reverts if index >= length
170
+ ```
171
+
172
+ ### Set
173
+
174
+ Write element at index:
175
+
176
+ ```typescript
177
+ // Set element at index
178
+ this.holders.set(index, newValue);
179
+ this.holders.save(); // Commit changes
180
+
181
+ // Reverts if index >= MAX_LENGTH
182
+ // Can set beyond current length (use push for proper length tracking)
183
+ ```
184
+
185
+ ### Length
186
+
187
+ Get current array length:
188
+
189
+ ```typescript
190
+ // Get length
191
+ const count: u32 = this.holders.getLength();
192
+
193
+ // Check if empty
194
+ if (this.holders.getLength() === 0) {
195
+ throw new Revert('No holders');
196
+ }
197
+ ```
198
+
199
+ ## Solidity vs OPNet Comparison
200
+
201
+ ### Quick Reference Table
202
+
203
+ | Solidity Array Type | OPNet Equivalent | Elements per Slot | Default Max |
204
+ |---------------------|------------------|-------------------|-------------|
205
+ | `uint256[]` | `StoredU256Array` | 1 | u32.MAX_VALUE - 1 |
206
+ | `uint128[]` | `StoredU128Array` | 2 | u32.MAX_VALUE - 1 |
207
+ | `uint64[]` | `StoredU64Array` | 4 | u32.MAX_VALUE - 1 |
208
+ | `uint32[]` | `StoredU32Array` | 8 | u32.MAX_VALUE - 1 |
209
+ | `uint16[]` | `StoredU16Array` | 16 | u32.MAX_VALUE - 1 |
210
+ | `uint8[]` / `bytes` | `StoredU8Array` | 32 | u32.MAX_VALUE - 1 |
211
+ | `address[]` | `StoredAddressArray` | 1 | u32.MAX_VALUE - 1 |
212
+ | `bool[]` | `StoredBooleanArray` | 256 (bit-packed) | u32.MAX_VALUE - 1 |
213
+
214
+ ### Operations Comparison
215
+
216
+ | Operation | Solidity | OPNet |
217
+ |-----------|----------|-------|
218
+ | Declare array | `address[] public holders;` | `private holders: StoredAddressArray;` |
219
+ | Initialize | Automatic | `this.holders = new StoredAddressArray(this.holdersPointer, EMPTY_POINTER);` |
220
+ | Push element | `holders.push(addr);` | `holders.push(addr); holders.save();` |
221
+ | Pop element | `holders.pop();` | `holders.deleteLast(); holders.save();` |
222
+ | Shift element | N/A | `holders.shift(); holders.save();` |
223
+ | Get element | `holders[i]` | `holders.get(i)` |
224
+ | Set element | `holders[i] = addr;` | `holders.set(i, addr); holders.save();` |
225
+ | Get length | `holders.length` | `holders.getLength()` |
226
+ | Delete at index | `delete holders[i];` | `holders.delete(i); holders.save();` |
227
+ | Check bounds | Runtime revert | Runtime revert |
228
+ | Clear array | `delete holders;` | `holders.deleteAll();` |
229
+ | Reset array | N/A | `holders.reset();` |
230
+
231
+ ### Common Patterns
232
+
233
+ | Pattern | Solidity | OPNet |
234
+ |---------|----------|-------|
235
+ | Loop through array | `for (uint i = 0; i < arr.length; i++)` | `for (let i: u32 = 0; i < arr.getLength(); i++)` |
236
+ | Remove at index (swap) | `arr[i] = arr[arr.length-1]; arr.pop();` | `arr.set(i, arr.get(arr.getLength()-1)); arr.deleteLast(); arr.save();` |
237
+ | Check if empty | `arr.length == 0` | `arr.getLength() === 0` |
238
+ | Get last element | `arr[arr.length - 1]` | `arr.get(arr.getLength() - 1)` |
239
+ | Initialize with values | `arr = [1, 2, 3];` | Multiple `arr.push()` calls in `onDeployment`, then `arr.save()` |
240
+
241
+ ### Full Example Comparison
242
+
243
+ ```solidity
244
+ // Solidity
245
+ contract Registry {
246
+ address[] public members;
247
+
248
+ function addMember(address member) external {
249
+ members.push(member);
250
+ }
251
+
252
+ function removeMember(uint256 index) external {
253
+ members[index] = members[members.length - 1];
254
+ members.pop();
255
+ }
256
+
257
+ function getMemberCount() external view returns (uint256) {
258
+ return members.length;
259
+ }
260
+ }
261
+ ```
262
+
263
+ ```typescript
264
+ // OPNet
265
+ @final
266
+ export class Registry extends OP_NET {
267
+ private membersPointer: u16 = Blockchain.nextPointer;
268
+ private members: StoredAddressArray;
269
+
270
+ constructor() {
271
+ super();
272
+ this.members = new StoredAddressArray(this.membersPointer, EMPTY_POINTER);
273
+ }
274
+
275
+ public addMember(calldata: Calldata): BytesWriter {
276
+ const member = calldata.readAddress();
277
+ this.members.push(member);
278
+ this.members.save();
279
+ return new BytesWriter(0);
280
+ }
281
+
282
+ public removeMember(calldata: Calldata): BytesWriter {
283
+ const index = calldata.readU32();
284
+ const length = this.members.getLength();
285
+
286
+ if (index >= length) {
287
+ throw new Revert('Index out of bounds');
288
+ }
289
+
290
+ if (index < length - 1) {
291
+ this.members.set(index, this.members.get(length - 1));
292
+ }
293
+ this.members.deleteLast();
294
+ this.members.save();
295
+
296
+ return new BytesWriter(0);
297
+ }
298
+
299
+ public getMemberCount(_calldata: Calldata): BytesWriter {
300
+ const writer = new BytesWriter(4);
301
+ writer.writeU32(this.members.getLength());
302
+ return writer;
303
+ }
304
+ }
305
+ ```
306
+
307
+ ## Side-by-Side Code Examples
308
+
309
+ ### Simple Address List
310
+
311
+ **Solidity:**
312
+ ```solidity
313
+ contract AddressList {
314
+ address[] public addresses;
315
+
316
+ function add(address addr) external {
317
+ addresses.push(addr);
318
+ }
319
+
320
+ function remove(uint256 index) external {
321
+ require(index < addresses.length, "Out of bounds");
322
+ addresses[index] = addresses[addresses.length - 1];
323
+ addresses.pop();
324
+ }
325
+
326
+ function get(uint256 index) external view returns (address) {
327
+ return addresses[index];
328
+ }
329
+
330
+ function count() external view returns (uint256) {
331
+ return addresses.length;
332
+ }
333
+
334
+ function contains(address addr) external view returns (bool) {
335
+ for (uint i = 0; i < addresses.length; i++) {
336
+ if (addresses[i] == addr) return true;
337
+ }
338
+ return false;
339
+ }
340
+ }
341
+ ```
342
+
343
+ **OPNet:**
344
+ ```typescript
345
+ @final
346
+ export class AddressList extends OP_NET {
347
+ private addressesPointer: u16 = Blockchain.nextPointer;
348
+ private addresses: StoredAddressArray;
349
+
350
+ constructor() {
351
+ super();
352
+ this.addresses = new StoredAddressArray(this.addressesPointer, EMPTY_POINTER);
353
+ }
354
+
355
+ public add(calldata: Calldata): BytesWriter {
356
+ const addr = calldata.readAddress();
357
+ this.addresses.push(addr);
358
+ this.addresses.save();
359
+ return new BytesWriter(0);
360
+ }
361
+
362
+ public remove(calldata: Calldata): BytesWriter {
363
+ const index = calldata.readU32();
364
+ const length = this.addresses.getLength();
365
+ if (index >= length) {
366
+ throw new Revert('Out of bounds');
367
+ }
368
+ if (index < length - 1) {
369
+ this.addresses.set(index, this.addresses.get(length - 1));
370
+ }
371
+ this.addresses.deleteLast();
372
+ this.addresses.save();
373
+ return new BytesWriter(0);
374
+ }
375
+
376
+ public get(calldata: Calldata): BytesWriter {
377
+ const index = calldata.readU32();
378
+ const writer = new BytesWriter(32);
379
+ writer.writeAddress(this.addresses.get(index));
380
+ return writer;
381
+ }
382
+
383
+ public count(_calldata: Calldata): BytesWriter {
384
+ const writer = new BytesWriter(4);
385
+ writer.writeU32(this.addresses.getLength());
386
+ return writer;
387
+ }
388
+
389
+ public contains(calldata: Calldata): BytesWriter {
390
+ const addr = calldata.readAddress();
391
+ let found = false;
392
+ const length = this.addresses.getLength();
393
+ for (let i: u32 = 0; i < length; i++) {
394
+ if (this.addresses.get(i).equals(addr)) {
395
+ found = true;
396
+ break;
397
+ }
398
+ }
399
+ const writer = new BytesWriter(1);
400
+ writer.writeBoolean(found);
401
+ return writer;
402
+ }
403
+ }
404
+ ```
405
+
406
+ ### Value Queue (FIFO-like with array)
407
+
408
+ **Solidity:**
409
+ ```solidity
410
+ contract ValueQueue {
411
+ uint256[] public values;
412
+
413
+ function enqueue(uint256 value) external {
414
+ values.push(value);
415
+ }
416
+
417
+ // Note: This is O(n) - not efficient for large queues
418
+ function dequeue() external returns (uint256) {
419
+ require(values.length > 0, "Empty queue");
420
+ uint256 first = values[0];
421
+ for (uint i = 0; i < values.length - 1; i++) {
422
+ values[i] = values[i + 1];
423
+ }
424
+ values.pop();
425
+ return first;
426
+ }
427
+
428
+ function peek() external view returns (uint256) {
429
+ require(values.length > 0, "Empty queue");
430
+ return values[0];
431
+ }
432
+
433
+ function size() external view returns (uint256) {
434
+ return values.length;
435
+ }
436
+ }
437
+ ```
438
+
439
+ **OPNet:**
440
+ ```typescript
441
+ @final
442
+ export class ValueQueue extends OP_NET {
443
+ private valuesPointer: u16 = Blockchain.nextPointer;
444
+ private values: StoredU256Array;
445
+
446
+ constructor() {
447
+ super();
448
+ this.values = new StoredU256Array(this.valuesPointer, EMPTY_POINTER);
449
+ }
450
+
451
+ public enqueue(calldata: Calldata): BytesWriter {
452
+ const value = calldata.readU256();
453
+ this.values.push(value);
454
+ this.values.save();
455
+ return new BytesWriter(0);
456
+ }
457
+
458
+ // Note: Use shift() for O(1) dequeue - it uses circular buffer with startIndex
459
+ public dequeue(_calldata: Calldata): BytesWriter {
460
+ const length = this.values.getLength();
461
+ if (length === 0) {
462
+ throw new Revert('Empty queue');
463
+ }
464
+ const first = this.values.shift(); // O(1) operation
465
+ this.values.save();
466
+
467
+ const writer = new BytesWriter(32);
468
+ writer.writeU256(first);
469
+ return writer;
470
+ }
471
+
472
+ public peek(_calldata: Calldata): BytesWriter {
473
+ if (this.values.getLength() === 0) {
474
+ throw new Revert('Empty queue');
475
+ }
476
+ const writer = new BytesWriter(32);
477
+ writer.writeU256(this.values.get(0));
478
+ return writer;
479
+ }
480
+
481
+ public size(_calldata: Calldata): BytesWriter {
482
+ const writer = new BytesWriter(4);
483
+ writer.writeU32(this.values.getLength());
484
+ return writer;
485
+ }
486
+ }
487
+ ```
488
+
489
+ ## Common Patterns
490
+
491
+ ### Iterating
492
+
493
+ ```typescript
494
+ // Forward iteration
495
+ const length = this.holders.getLength();
496
+ for (let i: u32 = 0; i < length; i++) {
497
+ const holder = this.holders.get(i);
498
+ // Process holder...
499
+ }
500
+
501
+ // Batch retrieval for efficiency
502
+ const batchSize: u32 = 100;
503
+ const allValues = this.values.getAll(0, batchSize);
504
+ for (let i = 0; i < allValues.length; i++) {
505
+ // Process allValues[i]...
506
+ }
507
+ ```
508
+
509
+ ### Finding Elements
510
+
511
+ ```typescript
512
+ // Find index of element
513
+ private indexOf(array: StoredAddressArray, target: Address): i32 {
514
+ const length = array.getLength();
515
+ for (let i: u32 = 0; i < length; i++) {
516
+ if (array.get(i).equals(target)) {
517
+ return i32(i);
518
+ }
519
+ }
520
+ return -1; // Not found
521
+ }
522
+
523
+ // Check if element exists
524
+ private contains(array: StoredAddressArray, target: Address): bool {
525
+ return this.indexOf(array, target) >= 0;
526
+ }
527
+ ```
528
+
529
+ ### Removing Elements
530
+
531
+ ```typescript
532
+ // Remove at index (swap with last, then deleteLast)
533
+ private removeAt(array: StoredAddressArray, index: u32): void {
534
+ const length = array.getLength();
535
+
536
+ if (index >= length) {
537
+ throw new Revert('Index out of bounds');
538
+ }
539
+
540
+ // If not last element, swap with last
541
+ if (index < length - 1) {
542
+ const last = array.get(length - 1);
543
+ array.set(index, last);
544
+ }
545
+
546
+ // Remove last element
547
+ array.deleteLast();
548
+ array.save();
549
+ }
550
+
551
+ // Remove by value
552
+ private removeValue(array: StoredAddressArray, value: Address): bool {
553
+ const idx = this.indexOf(array, value);
554
+ if (idx < 0) {
555
+ return false;
556
+ }
557
+ this.removeAt(array, u32(idx));
558
+ return true;
559
+ }
560
+
561
+ // Alternative: Use delete(index) to remove at specific index
562
+ // This sets the element to zero but doesn't shift other elements
563
+ ```
564
+
565
+ ### Unique Elements Set
566
+
567
+ ```typescript
568
+ // Add only if not present
569
+ private addUnique(array: StoredAddressArray, value: Address): bool {
570
+ if (this.contains(array, value)) {
571
+ return false; // Already exists
572
+ }
573
+
574
+ array.push(value);
575
+ array.save();
576
+ return true;
577
+ }
578
+ ```
579
+
580
+ ## Use Cases
581
+
582
+ ### Token Holder Tracking
583
+
584
+ ```typescript
585
+ @final
586
+ export class Token extends OP20 {
587
+ private holdersPointer: u16 = Blockchain.nextPointer;
588
+ private holders: StoredAddressArray;
589
+
590
+ constructor() {
591
+ super();
592
+ this.holders = new StoredAddressArray(this.holdersPointer, EMPTY_POINTER);
593
+ }
594
+
595
+ public override _transfer(from: Address, to: Address, amount: u256): void {
596
+ // Track new holders
597
+ if (this.balanceOf(to).isZero() && !amount.isZero()) {
598
+ this.holders.push(to);
599
+ this.holders.save();
600
+ }
601
+
602
+ super._transfer(from, to, amount);
603
+
604
+ // Note: Removing holders when balance becomes zero
605
+ // requires additional logic (holder index mapping)
606
+ }
607
+
608
+ public getHolderCount(_calldata: Calldata): BytesWriter {
609
+ const writer = new BytesWriter(4);
610
+ writer.writeU32(this.holders.getLength());
611
+ return writer;
612
+ }
613
+ }
614
+ ```
615
+
616
+ ### Order Queue
617
+
618
+ ```typescript
619
+ @final
620
+ export class OrderBook extends OP_NET {
621
+ private ordersPointer: u16 = Blockchain.nextPointer;
622
+ private orders: StoredU256Array;
623
+
624
+ constructor() {
625
+ super();
626
+ this.orders = new StoredU256Array(this.ordersPointer, EMPTY_POINTER);
627
+ }
628
+
629
+ public addOrder(calldata: Calldata): BytesWriter {
630
+ const orderId = calldata.readU256();
631
+ this.orders.push(orderId);
632
+ this.orders.save();
633
+ return new BytesWriter(0);
634
+ }
635
+
636
+ public processNextOrder(_calldata: Calldata): BytesWriter {
637
+ if (this.orders.getLength() === 0) {
638
+ throw new Revert('No orders');
639
+ }
640
+
641
+ // FIFO: Use shift() to get first order (O(1) with circular buffer)
642
+ const orderId = this.orders.shift();
643
+ this.orders.save();
644
+
645
+ // Process order...
646
+
647
+ const writer = new BytesWriter(32);
648
+ writer.writeU256(orderId);
649
+ return writer;
650
+ }
651
+ }
652
+ ```
653
+
654
+ ### Whitelist Management
655
+
656
+ ```typescript
657
+ @final
658
+ export class Whitelist extends OP_NET {
659
+ private addressesPointer: u16 = Blockchain.nextPointer;
660
+ private addresses: StoredAddressArray;
661
+
662
+ constructor() {
663
+ super();
664
+ this.addresses = new StoredAddressArray(this.addressesPointer, EMPTY_POINTER);
665
+ }
666
+
667
+ public add(calldata: Calldata): BytesWriter {
668
+ this.onlyDeployer(Blockchain.tx.sender);
669
+
670
+ const addr = calldata.readAddress();
671
+
672
+ // Check not already in list
673
+ const length = this.addresses.getLength();
674
+ for (let i: u32 = 0; i < length; i++) {
675
+ if (this.addresses.get(i).equals(addr)) {
676
+ throw new Revert('Already whitelisted');
677
+ }
678
+ }
679
+
680
+ this.addresses.push(addr);
681
+ this.addresses.save();
682
+ return new BytesWriter(0);
683
+ }
684
+
685
+ public isWhitelisted(calldata: Calldata): BytesWriter {
686
+ const addr = calldata.readAddress();
687
+
688
+ let found = false;
689
+ const length = this.addresses.getLength();
690
+ for (let i: u32 = 0; i < length; i++) {
691
+ if (this.addresses.get(i).equals(addr)) {
692
+ found = true;
693
+ break;
694
+ }
695
+ }
696
+
697
+ const writer = new BytesWriter(1);
698
+ writer.writeBoolean(found);
699
+ return writer;
700
+ }
701
+ }
702
+ ```
703
+
704
+ ## Best Practices
705
+
706
+ ### 1. Limit Array Size
707
+
708
+ ```typescript
709
+ const MAX_ARRAY_SIZE: u32 = 1000;
710
+
711
+ public addItem(calldata: Calldata): BytesWriter {
712
+ if (this.items.getLength() >= MAX_ARRAY_SIZE) {
713
+ throw new Revert('Array size limit reached');
714
+ }
715
+ this.items.push(calldata.readU256());
716
+ this.items.save();
717
+ return new BytesWriter(0);
718
+ }
719
+ ```
720
+
721
+ ### 2. Cache Length in Loops
722
+
723
+ ```typescript
724
+ // Good: Cache length
725
+ const length = this.items.getLength();
726
+ for (let i: u32 = 0; i < length; i++) {
727
+ // ...
728
+ }
729
+
730
+ // Even better: Use getAll() for batch operations
731
+ const items = this.items.getAll(0, 100);
732
+ for (let i = 0; i < items.length; i++) {
733
+ // Process items[i]
734
+ }
735
+ ```
736
+
737
+ ### 3. Use Maps for Lookup-Heavy Cases
738
+
739
+ If you frequently check "is X in array?", consider using a map alongside the array:
740
+
741
+ ```typescript
742
+ private itemsPointer: u16 = Blockchain.nextPointer;
743
+ private itemExistsPointer: u16 = Blockchain.nextPointer;
744
+
745
+ private items: StoredU256Array;
746
+ private itemExists: StoredMapU256;
747
+
748
+ constructor() {
749
+ super();
750
+ this.items = new StoredU256Array(this.itemsPointer, EMPTY_POINTER);
751
+ this.itemExists = new StoredMapU256(this.itemExistsPointer);
752
+ }
753
+
754
+ public addItem(item: u256): void {
755
+ if (!this.itemExists.get(item).isZero()) {
756
+ throw new Revert('Already exists');
757
+ }
758
+ this.items.push(item);
759
+ this.items.save();
760
+ this.itemExists.set(item, u256.One);
761
+ }
762
+ ```
763
+
764
+ ### 4. Always Call save() After Modifications
765
+
766
+ ```typescript
767
+ // Multiple modifications, single save
768
+ this.items.push(value1);
769
+ this.items.push(value2);
770
+ this.items.set(0, value3);
771
+ this.items.save(); // Commit all changes at once
772
+ ```
773
+
774
+ ---
775
+
776
+ **Navigation:**
777
+ - Previous: [Stored Primitives](./stored-primitives.md)
778
+ - Next: [Stored Maps](./stored-maps.md)