@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.
- package/package.json +2 -2
- package/runtime/contracts/OP20.ts +19 -8
- package/runtime/contracts/OP721.ts +19 -8
- package/runtime/env/BlockchainEnvironment.ts +90 -65
- package/runtime/env/global.ts +24 -14
- package/runtime/generic/AddressMap.ts +113 -24
- package/runtime/generic/Map.ts +78 -22
- package/runtime/generic/MapUint8Array.ts +115 -27
- package/runtime/storage/arrays/StoredPackedArray.ts +117 -60
- package/runtime/types/ExtendedAddress.ts +4 -4
- package/runtime/types/SafeMath.ts +240 -337
|
@@ -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
|
-
*
|
|
15
|
+
* and how to pack/unpack them.
|
|
21
16
|
*
|
|
22
|
-
*
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
407
|
-
|
|
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
|
-
|
|
428
|
-
|
|
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());
|
|
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
|
-
|
|
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
|
|
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,
|
|
530
|
+
this._slots.set(slotIndex, storageData);
|
|
531
|
+
data = storageData;
|
|
495
532
|
}
|
|
496
533
|
|
|
497
|
-
|
|
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
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
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
|
/**
|