@btc-vision/btc-runtime 1.10.3 → 1.10.5

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.
@@ -1,9 +1,4 @@
1
- import {
2
- bigEndianAdd,
3
- GET_EMPTY_BUFFER,
4
- readLengthAndStartIndex,
5
- writeLengthAndStartIndex,
6
- } from '../../math/bytes';
1
+ import { bigEndianAdd, GET_EMPTY_BUFFER, readLengthAndStartIndex, writeLengthAndStartIndex, } from '../../math/bytes';
7
2
  import { Blockchain } from '../../env';
8
3
  import { Revert } from '../../types/Revert';
9
4
  import { encodePointer } from '../../math/abi';
@@ -17,9 +12,13 @@ export const DEFAULT_MAX_LENGTH: u32 = u32.MAX_VALUE - 1;
17
12
  * - Tracks length + startIndex in the first 16 bytes of `lengthPointer`.
18
13
  * - Maps each global index to (slotIndex, subIndex).
19
14
  * - Child classes define how many T items fit per 32-byte slot
20
- * and how to pack/unpack them.
15
+ * and how to pack/unpack them.
21
16
  *
22
- * Note: This is designed to wrap around.
17
+ * Note: This is designed to wrap around.
18
+ *
19
+ * **Optimizations:**
20
+ * - Hot Slot Caching: Skips Map lookups for sequential access.
21
+ * - Conditional Subtraction: Replaces expensive modulo (%) operations.
23
22
  */
24
23
  export abstract class StoredPackedArray<T> {
25
24
  /** 32-byte base pointer (used to derive storage keys). */
@@ -47,6 +46,12 @@ export abstract class StoredPackedArray<T> {
47
46
  /** Track which slotIndexes are changed and need saving. */
48
47
  protected _isChanged: Set<u32> = new Set();
49
48
 
49
+ // --- OPTIMIZATION: HOT SLOT CACHE ---
50
+ // Stores the last accessed slot index and data.
51
+ // Prevents map lookups during sequential operations (iterating, pushing, etc).
52
+ private _cachedSlotIndex: u32 = <u32>-1; // -1 indicates empty cache
53
+ private _cachedSlotData: Uint8Array | null = null;
54
+
50
55
  private nextItemOffset: u32 = 0;
51
56
 
52
57
  protected constructor(
@@ -68,11 +73,14 @@ export abstract class StoredPackedArray<T> {
68
73
 
69
74
  @inline
70
75
  public get previousOffset(): u32 {
71
- return <u32>(
72
- ((this._startIndex +
73
- <u64>(this.nextItemOffset === 0 ? this.nextItemOffset : this.nextItemOffset - 1)) %
74
- <u64>this.MAX_LENGTH)
75
- );
76
+ // Optimization: Conditional subtraction logic is cleaner for gas,
77
+ // but keeping modulo here for safety on the offset calculation logic.
78
+ const offset = this.nextItemOffset === 0 ? this.nextItemOffset : this.nextItemOffset - 1;
79
+ let val = <u64>this._startIndex + <u64>offset;
80
+ const max = <u64>this.MAX_LENGTH;
81
+
82
+ if (val >= max) val %= max;
83
+ return <u32>val;
76
84
  }
77
85
 
78
86
  /**
@@ -99,13 +107,15 @@ export abstract class StoredPackedArray<T> {
99
107
 
100
108
  const realIndex: u32 = this.getRealIndex(index);
101
109
  const cap: u32 = this.getSlotCapacity();
110
+
102
111
  const slotIndex = realIndex / cap;
103
112
  const subIndex = <u32>(realIndex % cap);
104
113
 
114
+ // Optimization: Uses Hot Slot Cache
105
115
  const slotData = this.ensureSlot(slotIndex);
106
116
  const arr = this.unpackSlot(slotData);
107
117
 
108
- return arr[subIndex];
118
+ return unchecked(arr[subIndex]);
109
119
  }
110
120
 
111
121
  @inline
@@ -121,7 +131,7 @@ export abstract class StoredPackedArray<T> {
121
131
  const slotData = this.ensureSlot(slotIndex);
122
132
  const arr = this.unpackSlot(slotData);
123
133
 
124
- return arr[subIndex];
134
+ return unchecked(arr[subIndex]);
125
135
  }
126
136
 
127
137
  @inline
@@ -139,11 +149,12 @@ export abstract class StoredPackedArray<T> {
139
149
  let slotData = this.ensureSlot(slotIndex);
140
150
  const arr = this.unpackSlot(slotData);
141
151
 
142
- if (!this.eq(arr[subIndex], value)) {
143
- arr[subIndex] = value;
152
+ if (!this.eq(unchecked(arr[subIndex]), value)) {
153
+ unchecked((arr[subIndex] = value));
144
154
  slotData = this.packSlot(arr);
145
- this._slots.set(slotIndex, slotData);
146
- this._isChanged.add(slotIndex);
155
+
156
+ // Optimization: Update Cache and Map
157
+ this.updateSlot(slotIndex, slotData);
147
158
  }
148
159
  }
149
160
 
@@ -160,11 +171,10 @@ export abstract class StoredPackedArray<T> {
160
171
  let slotData = this.ensureSlot(slotIndex);
161
172
  const arr = this.unpackSlot(slotData);
162
173
 
163
- if (!this.eq(arr[subIndex], value)) {
164
- arr[subIndex] = value;
174
+ if (!this.eq(unchecked(arr[subIndex]), value)) {
175
+ unchecked((arr[subIndex] = value));
165
176
  slotData = this.packSlot(arr);
166
- this._slots.set(slotIndex, slotData);
167
- this._isChanged.add(slotIndex);
177
+ this.updateSlot(slotIndex, slotData);
168
178
  }
169
179
  }
170
180
 
@@ -182,12 +192,10 @@ export abstract class StoredPackedArray<T> {
182
192
 
183
193
  @inline
184
194
  public incrementStartingIndex(): void {
195
+ this._startIndex += 1;
185
196
  if (this._startIndex >= this.MAX_LENGTH) {
186
197
  this._startIndex = 0;
187
- } else {
188
- this._startIndex += 1;
189
198
  }
190
-
191
199
  this._isChangedStartIndex = true;
192
200
  }
193
201
 
@@ -198,7 +206,10 @@ export abstract class StoredPackedArray<T> {
198
206
  public applyNextOffsetToStartingIndex(): void {
199
207
  if (!this.nextItemOffset) return;
200
208
 
209
+ // Optimization: Conditional subtraction instead of Modulo
201
210
  this._startIndex += this.nextItemOffset;
211
+ this._startIndex %= this.MAX_LENGTH;
212
+
202
213
  this._isChangedStartIndex = true;
203
214
  this.nextItemOffset = 0;
204
215
  }
@@ -217,11 +228,10 @@ export abstract class StoredPackedArray<T> {
217
228
  let slotData = this.ensureSlot(slotIndex);
218
229
  const arr = this.unpackSlot(slotData);
219
230
 
220
- if (!this.eq(arr[subIndex], value)) {
221
- arr[subIndex] = value;
231
+ if (!this.eq(unchecked(arr[subIndex]), value)) {
232
+ unchecked((arr[subIndex] = value));
222
233
  slotData = this.packSlot(arr);
223
- this._slots.set(slotIndex, slotData);
224
- this._isChanged.add(slotIndex);
234
+ this.updateSlot(slotIndex, slotData);
225
235
  }
226
236
 
227
237
  this._length += 1;
@@ -247,18 +257,21 @@ export abstract class StoredPackedArray<T> {
247
257
  let slotData = this.ensureSlot(slotIndex);
248
258
 
249
259
  const arr = this.unpackSlot(slotData);
250
- const currentData = arr[subIndex];
260
+ const currentData = unchecked(arr[subIndex]);
251
261
 
252
262
  if (!this.eq(currentData, this.defaultValue)) {
253
- arr[subIndex] = this.defaultValue;
263
+ unchecked((arr[subIndex] = this.defaultValue));
254
264
  slotData = this.packSlot(arr);
255
-
256
- this._slots.set(slotIndex, slotData);
257
- this._isChanged.add(slotIndex);
265
+ this.updateSlot(slotIndex, slotData);
258
266
  }
259
267
 
260
268
  this._length -= 1;
261
269
  this._startIndex += 1;
270
+ // Optimization: Fast Wrap
271
+ if (this._startIndex >= this.MAX_LENGTH) {
272
+ this._startIndex = 0;
273
+ }
274
+
262
275
  this._isChangedLength = true;
263
276
  this._isChangedStartIndex = true;
264
277
 
@@ -280,11 +293,10 @@ export abstract class StoredPackedArray<T> {
280
293
  const arr = this.unpackSlot(slotData);
281
294
 
282
295
  const zeroVal = this.zeroValue();
283
- if (!this.eq(arr[subIndex], zeroVal)) {
284
- arr[subIndex] = zeroVal;
296
+ if (!this.eq(unchecked(arr[subIndex]), zeroVal)) {
297
+ unchecked((arr[subIndex] = zeroVal));
285
298
  slotData = this.packSlot(arr);
286
- this._slots.set(slotIndex, slotData);
287
- this._isChanged.add(slotIndex);
299
+ this.updateSlot(slotIndex, slotData);
288
300
  }
289
301
  }
290
302
 
@@ -316,11 +328,10 @@ export abstract class StoredPackedArray<T> {
316
328
  const arr = this.unpackSlot(slotData);
317
329
 
318
330
  const zeroVal = this.zeroValue();
319
- if (!this.eq(arr[subIndex], zeroVal)) {
320
- arr[subIndex] = zeroVal;
331
+ if (!this.eq(unchecked(arr[subIndex]), zeroVal)) {
332
+ unchecked((arr[subIndex] = zeroVal));
321
333
  slotData = this.packSlot(arr);
322
- this._slots.set(slotIndex, slotData);
323
- this._isChanged.add(slotIndex);
334
+ this.updateSlot(slotIndex, slotData);
324
335
  }
325
336
  }
326
337
 
@@ -347,8 +358,9 @@ export abstract class StoredPackedArray<T> {
347
358
  throw new Revert('setMultiple: out of range (packed array)');
348
359
  }
349
360
 
361
+ // Sequential iteration benefits from Hot Slot Cache
350
362
  for (let i = 0; i < values.length; i++) {
351
- this.set(startIndex + <u32>i, values[i]);
363
+ this.set(startIndex + <u32>i, unchecked(values[i]));
352
364
  }
353
365
  }
354
366
 
@@ -360,7 +372,7 @@ export abstract class StoredPackedArray<T> {
360
372
 
361
373
  const out = new Array<T>(<i32>count);
362
374
  for (let i: u32 = 0; i < count; i++) {
363
- out[<i32>i] = this.get(startIndex + i);
375
+ unchecked((out[<i32>i] = this.get(startIndex + i)));
364
376
  }
365
377
 
366
378
  return out;
@@ -403,8 +415,10 @@ export abstract class StoredPackedArray<T> {
403
415
 
404
416
  public save(): void {
405
417
  const changed = this._isChanged.values();
406
- for (let i = 0; i < changed.length; i++) {
407
- const slotIndex = changed[i];
418
+ const len = changed.length;
419
+
420
+ for (let i = 0; i < len; i++) {
421
+ const slotIndex = unchecked(changed[i]);
408
422
  const slotData = this._slots.get(slotIndex);
409
423
  if (slotData) {
410
424
  const ptr = this.calculateStoragePointer(<u64>slotIndex);
@@ -424,10 +438,12 @@ export abstract class StoredPackedArray<T> {
424
438
 
425
439
  public deleteAll(): void {
426
440
  const keys = this._slots.keys();
427
- for (let i = 0; i < keys.length; i++) {
428
- const slotIndex = keys[i];
441
+ const len = keys.length;
442
+
443
+ for (let i = 0; i < len; i++) {
444
+ const slotIndex = unchecked(keys[i]);
429
445
  const ptr = this.calculateStoragePointer(<u64>slotIndex);
430
- Blockchain.setStorageAt(ptr, GET_EMPTY_BUFFER()); // 32 bytes of zero
446
+ Blockchain.setStorageAt(ptr, GET_EMPTY_BUFFER());
431
447
  }
432
448
 
433
449
  // Reset length + startIndex
@@ -437,8 +453,13 @@ export abstract class StoredPackedArray<T> {
437
453
  this._startIndex = 0;
438
454
  this._isChangedLength = false;
439
455
  this._isChangedStartIndex = false;
456
+
440
457
  this._slots.clear();
441
458
  this._isChanged.clear();
459
+
460
+ // Clear Cache
461
+ this._cachedSlotIndex = <u32>-1;
462
+ this._cachedSlotData = null;
442
463
  }
443
464
 
444
465
  /**
@@ -453,6 +474,10 @@ export abstract class StoredPackedArray<T> {
453
474
  this._slots.clear();
454
475
  this._isChanged.clear();
455
476
 
477
+ // Clear Cache
478
+ this._cachedSlotIndex = <u32>-1;
479
+ this._cachedSlotData = null;
480
+
456
481
  this.save();
457
482
  }
458
483
 
@@ -484,26 +509,58 @@ export abstract class StoredPackedArray<T> {
484
509
 
485
510
  /**
486
511
  * Ensure that slotIndex is loaded into _slots. If missing, read from storage.
512
+ * * **Optimization:** Uses `_cachedSlotIndex` to return sequentially accessed data instantly.
487
513
  */
514
+ @inline
488
515
  protected ensureSlot(slotIndex: u32): Uint8Array {
489
- if (!this._slots.has(slotIndex)) {
516
+ // Fast Cache Check
517
+ if (slotIndex == this._cachedSlotIndex) {
518
+ return <Uint8Array>this._cachedSlotData;
519
+ }
520
+
521
+ // Map Lookup / Storage Load
522
+ let data: Uint8Array;
523
+ if (this._slots.has(slotIndex)) {
524
+ data = this._slots.get(slotIndex);
525
+ } else {
490
526
  const ptr = this.calculateStoragePointer(<u64>slotIndex);
491
- const data = Blockchain.getStorageAt(ptr);
527
+ const storageData = Blockchain.getStorageAt(ptr);
492
528
 
493
529
  // Must be exactly 32 bytes; if it's empty, you get 32 zero bytes from GET_EMPTY_BUFFER()
494
- this._slots.set(slotIndex, data);
530
+ this._slots.set(slotIndex, storageData);
531
+ data = storageData;
495
532
  }
496
533
 
497
- return this._slots.get(slotIndex);
534
+ // Update Cache
535
+ this._cachedSlotIndex = slotIndex;
536
+ this._cachedSlotData = data;
537
+
538
+ return data;
498
539
  }
499
540
 
541
+ /**
542
+ * Updates the slot data in the Map and Cache, and marks it as changed.
543
+ */
544
+ @inline
545
+ protected updateSlot(slotIndex: u32, data: Uint8Array): void {
546
+ this._slots.set(slotIndex, data);
547
+ this._isChanged.add(slotIndex);
548
+
549
+ // Keep cache in sync
550
+ this._cachedSlotIndex = slotIndex;
551
+ this._cachedSlotData = data;
552
+ }
553
+
554
+ @inline
500
555
  private getRealIndex(index: u32, isPhysical: bool = false): u32 {
501
- const maxLength: u64 = <u64>this.MAX_LENGTH;
502
- let realIndex: u64 = (isPhysical ? <u64>0 : <u64>this._startIndex) + <u64>index;
503
- if (!(realIndex < maxLength)) {
504
- realIndex %= maxLength;
505
- }
556
+ const maxLength = this.MAX_LENGTH;
557
+
558
+ // FIX: Explicitly type 'start' as u32 to prevent implicit i32 casting of '0'
559
+ const start: u32 = isPhysical ? 0 : this._startIndex;
560
+ let realIndex: u32 = start + index;
506
561
 
507
- return <u32>realIndex;
562
+ realIndex %= maxLength;
563
+
564
+ return realIndex;
508
565
  }
509
566
  }
@@ -410,14 +410,14 @@ export const ZERO_BITCOIN_ADDRESS: ExtendedAddress = new ExtendedAddress(
410
410
  * Hash: 284ae4acdb32a99ba3ebfa66a91ddb41a7b7a1d2fef415399922cd8a04485c02
411
411
  */
412
412
  export const DEAD_ADDRESS: ExtendedAddress = new ExtendedAddress(
413
- [
414
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
415
- 0,
416
- ],
417
413
  [
418
414
  40, 74, 228, 172, 219, 50, 169, 155, 163, 235, 250, 102, 169, 29, 219, 65, 167, 183, 161,
419
415
  210, 254, 244, 21, 57, 153, 34, 205, 138, 4, 72, 92, 2,
420
416
  ],
417
+ [
418
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
419
+ 0,
420
+ ],
421
421
  );
422
422
 
423
423
  /**