@btc-vision/btc-runtime 1.3.8 → 1.3.10

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.
@@ -0,0 +1,472 @@
1
+ import { u256 } from 'as-bignum/assembly';
2
+ import { Blockchain } from '../env';
3
+ import { BytesWriter } from '../buffer/BytesWriter';
4
+ import { SafeMath } from '../types/SafeMath';
5
+ import { Revert } from '../types/Revert';
6
+
7
+ /**
8
+ * @class StoredU16Array
9
+ * @description Manages an array of u16 values across multiple storage slots. Each slot holds sixteen u16 values packed into a u256.
10
+ */
11
+ @final
12
+ export class StoredU16Array {
13
+ private readonly baseU256Pointer: u256;
14
+ private readonly lengthPointer: u256;
15
+
16
+ // Internal cache for storage slots
17
+ private _values: Map<u64, u16[]> = new Map(); // Map from slotIndex to array of sixteen u16s
18
+ private _isLoaded: Set<u64> = new Set(); // Set of slotIndexes that are loaded
19
+ private _isChanged: Set<u64> = new Set(); // Set of slotIndexes that are modified
20
+
21
+ // Internal variables for length and startIndex management
22
+ private _length: u64 = 0; // Current length of the array
23
+ private _startIndex: u64 = 0; // Starting index of the array
24
+ private _isChangedLength: bool = false; // Indicates if the length has been modified
25
+ private _isChangedStartIndex: bool = false; // Indicates if the startIndex has been modified
26
+
27
+ // Define a maximum allowed length to prevent excessive storage usage
28
+ private readonly MAX_LENGTH: u64 = u64.MAX_VALUE - 1;
29
+
30
+ /**
31
+ * @constructor
32
+ * @param {u16} pointer - The primary pointer identifier.
33
+ * @param {Uint8Array} subPointer - The sub-pointer for memory slot addressing.
34
+ * @param {u256} defaultValue - The default u256 value if storage is uninitialized.
35
+ */
36
+ constructor(
37
+ public pointer: u16,
38
+ public subPointer: Uint8Array,
39
+ private defaultValue: u256,
40
+ ) {
41
+ // Initialize the base u256 pointer using the primary pointer and subPointer
42
+ const writer = new BytesWriter(32);
43
+ writer.writeU16(pointer);
44
+ writer.writeBytes(subPointer);
45
+
46
+ // Initialize the base and length pointers
47
+ const baseU256Pointer = u256.fromBytes(writer.getBuffer(), true);
48
+ const lengthPointer = baseU256Pointer.clone();
49
+
50
+ // Load the current length and startIndex from storage
51
+ const storedLengthAndStartIndex: u256 = Blockchain.getStorageAt(lengthPointer, u256.Zero);
52
+ this.lengthPointer = lengthPointer;
53
+ this.baseU256Pointer = baseU256Pointer;
54
+
55
+ this._length = storedLengthAndStartIndex.lo1; // Bytes 0-7: length
56
+ this._startIndex = storedLengthAndStartIndex.lo2; // Bytes 8-15: startIndex
57
+ }
58
+
59
+ /**
60
+ * @method get
61
+ * @description Retrieves the u16 value at the specified global index.
62
+ * @param {u64} index - The global index (0 to ∞) of the u16 value to retrieve.
63
+ * @returns {u16} - The u16 value at the specified index.
64
+ */
65
+ @inline
66
+ public get(index: u64): u16 {
67
+ assert(index < this._length, 'Index out of bounds');
68
+
69
+ const slotIndex: u64 = index / 16; // Each slot holds sixteen u16s
70
+ const subIndex: u8 = <u8>(index % 16); // 0 to 15
71
+ this.ensureValues(slotIndex);
72
+ const slotValues = this._values.get(slotIndex);
73
+ return slotValues ? slotValues[subIndex] : 0;
74
+ }
75
+
76
+ /**
77
+ * @method set
78
+ * @description Sets the u16 value at the specified global index.
79
+ * @param {u64} index - The global index (0 to ∞) of the u16 value to set.
80
+ * @param {u16} value - The u16 value to assign.
81
+ */
82
+ @inline
83
+ public set(index: u64, value: u16): void {
84
+ assert(index < this._length, 'Index exceeds current array length');
85
+ const slotIndex: u64 = index / 16;
86
+ const subIndex: u8 = <u8>(index % 16);
87
+ this.ensureValues(slotIndex);
88
+
89
+ const slotValues = this._values.get(slotIndex);
90
+ if (slotValues && slotValues[subIndex] !== value) {
91
+ slotValues[subIndex] = value;
92
+ this._isChanged.add(slotIndex);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * @method push
98
+ * @description Appends a new u16 value to the end of the array.
99
+ * @param {u16} value - The u16 value to append.
100
+ */
101
+ public push(value: u16): void {
102
+ if (this._length >= this.MAX_LENGTH) {
103
+ throw new Revert(
104
+ 'Push operation failed: Array has reached its maximum allowed length.',
105
+ );
106
+ }
107
+
108
+ const newIndex: u64 = this._length;
109
+ const effectiveIndex: u64 = this._startIndex + newIndex;
110
+ const wrappedIndex: u64 =
111
+ effectiveIndex < this.MAX_LENGTH ? effectiveIndex : effectiveIndex % this.MAX_LENGTH;
112
+ const slotIndex: u64 = wrappedIndex / 16;
113
+ const subIndex: u8 = <u8>(wrappedIndex % 16);
114
+
115
+ // Ensure the slot is loaded
116
+ this.ensureValues(slotIndex);
117
+
118
+ // Set the new value
119
+ const slotValues = this._values.get(slotIndex);
120
+ if (slotValues) {
121
+ slotValues[subIndex] = value;
122
+ this._isChanged.add(slotIndex);
123
+ }
124
+
125
+ // Increment the length
126
+ this._length += 1;
127
+ this._isChangedLength = true;
128
+ }
129
+
130
+ /**
131
+ * @method delete
132
+ * @description Deletes the u16 value at the specified index by setting it to zero. Does not reorder the array.
133
+ * @param {u64} index - The global index of the u16 value to delete.
134
+ */
135
+ public delete(index: u64): void {
136
+ if (index >= this._length) {
137
+ throw new Revert('Delete operation failed: Index out of bounds.');
138
+ }
139
+
140
+ const slotIndex: u64 = index / 16;
141
+ const subIndex: u8 = <u8>(index % 16);
142
+ this.ensureValues(slotIndex);
143
+
144
+ const slotValues = this._values.get(slotIndex);
145
+ if (slotValues && slotValues[subIndex] !== 0) {
146
+ slotValues[subIndex] = 0;
147
+ this._isChanged.add(slotIndex);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * @method shift
153
+ * @description Removes the first element of the array by setting it to zero, decrementing the length, and incrementing the startIndex.
154
+ * If the startIndex reaches the maximum value of u64, it wraps around to 0.
155
+ */
156
+ public shift(): void {
157
+ if (this._length === 0) {
158
+ throw new Revert('Shift operation failed: Array is empty.');
159
+ }
160
+
161
+ const currentStartIndex: u64 = this._startIndex;
162
+ const slotIndex: u64 = currentStartIndex / 16;
163
+ const subIndex: u8 = <u8>(currentStartIndex % 16);
164
+ this.ensureValues(slotIndex);
165
+
166
+ const slotValues = this._values.get(slotIndex);
167
+ if (slotValues && slotValues[subIndex] !== 0) {
168
+ slotValues[subIndex] = 0;
169
+ this._isChanged.add(slotIndex);
170
+ }
171
+
172
+ // Decrement the length
173
+ this._length -= 1;
174
+ this._isChangedLength = true;
175
+
176
+ // Increment the startIndex with wrap-around
177
+ if (this._startIndex < this.MAX_LENGTH - 1) {
178
+ this._startIndex += 1;
179
+ } else {
180
+ this._startIndex = 0;
181
+ }
182
+ this._isChangedStartIndex = true;
183
+ }
184
+
185
+ /**
186
+ * @method save
187
+ * @description Persists all cached u16 values, the length, and the startIndex to their respective storage slots if any have been modified.
188
+ */
189
+ public save(): void {
190
+ // Save all changed slots
191
+ const values = this._isChanged.values();
192
+ for (let i = 0; i < values.length; i++) {
193
+ const slotIndex = values[i];
194
+ const packed = this.packValues(slotIndex);
195
+ const storagePointer = this.calculateStoragePointer(slotIndex);
196
+ Blockchain.setStorageAt(storagePointer, packed);
197
+ }
198
+ this._isChanged.clear();
199
+
200
+ // Save length and startIndex if changed
201
+ if (this._isChangedLength || this._isChangedStartIndex) {
202
+ const packedLengthAndStartIndex = new u256();
203
+ packedLengthAndStartIndex.lo1 = this._length;
204
+ packedLengthAndStartIndex.lo2 = this._startIndex;
205
+ Blockchain.setStorageAt(this.lengthPointer, packedLengthAndStartIndex);
206
+ this._isChangedLength = false;
207
+ this._isChangedStartIndex = false;
208
+ }
209
+ }
210
+
211
+ /**
212
+ * @method deleteAll
213
+ * @description Deletes all storage slots by setting them to zero, including the length and startIndex slots.
214
+ */
215
+ public deleteAll(): void {
216
+ // Iterate over all loaded slots and clear them
217
+ const keys = this._values.keys();
218
+ for (let i = 0; i < keys.length; i++) {
219
+ const slotIndex = keys[i];
220
+ const storagePointer = this.calculateStoragePointer(slotIndex);
221
+ Blockchain.setStorageAt(storagePointer, u256.Zero);
222
+ }
223
+
224
+ // Reset the length and startIndex to zero
225
+ const zeroLengthAndStartIndex = u256.Zero;
226
+ Blockchain.setStorageAt(this.lengthPointer, zeroLengthAndStartIndex);
227
+ this._length = 0;
228
+ this._startIndex = 0;
229
+ this._isChangedLength = false;
230
+ this._isChangedStartIndex = false;
231
+
232
+ // Clear internal caches
233
+ this._values.clear();
234
+ this._isLoaded.clear();
235
+ this._isChanged.clear();
236
+ }
237
+
238
+ /**
239
+ * @method setMultiple
240
+ * @description Sets multiple u16 values starting from a specific global index.
241
+ * @param {u64} startIndex - The starting global index.
242
+ * @param {u16[]} values - An array of u16 values to set.
243
+ */
244
+ @inline
245
+ public setMultiple(startIndex: u64, values: u16[]): void {
246
+ for (let i: u64 = 0; i < values.length; i++) {
247
+ this.set(startIndex + i, values[i]);
248
+ }
249
+ }
250
+
251
+ /**
252
+ * @method getAll
253
+ * @description Retrieves a range of u16 values starting from a specific global index.
254
+ * @param {u64} startIndex - The starting global index.
255
+ * @param {u64} count - The number of u16 values to retrieve.
256
+ * @returns {u16[]} - An array containing the retrieved u16 values.
257
+ */
258
+ @inline
259
+ public getAll(startIndex: u64, count: u64): u16[] {
260
+ assert(startIndex + count <= this._length, 'Requested range exceeds array length');
261
+ const result: u16[] = new Array<u16>(count);
262
+ for (let i: u64 = 0; i < count; i++) {
263
+ result[i] = this.get(startIndex + i);
264
+ }
265
+ return result;
266
+ }
267
+
268
+ /**
269
+ * @method toString
270
+ * @description Returns a string representation of all cached u16 values.
271
+ * @returns {string} - A string in the format "[value0, value1, ..., valueN]".
272
+ */
273
+ @inline
274
+ public toString(): string {
275
+ let str = '[';
276
+ for (let i: u64 = 0; i < this._length; i++) {
277
+ const value = this.get(i);
278
+ str += value.toString();
279
+ if (i !== this._length - 1) {
280
+ str += ', ';
281
+ }
282
+ }
283
+ str += ']';
284
+ return str;
285
+ }
286
+
287
+ /**
288
+ * @method toBytes
289
+ * @description Returns the packed u256 values as a byte array.
290
+ * @returns {u8[]} - The packed u256 values in byte form.
291
+ */
292
+ @inline
293
+ public toBytes(): u8[] {
294
+ const bytes: u8[] = new Array<u8>();
295
+ const slotCount: u64 = (this._length + 15) / 16;
296
+
297
+ for (let slotIndex: u64 = 0; slotIndex < slotCount; slotIndex++) {
298
+ this.ensureValues(slotIndex);
299
+ const packed = this.packValues(slotIndex);
300
+ const slotBytes = packed.toBytes();
301
+ for (let i: u32 = 0; i < slotBytes.length; i++) {
302
+ bytes.push(slotBytes[i]);
303
+ }
304
+ }
305
+ return bytes;
306
+ }
307
+
308
+ /**
309
+ * @method reset
310
+ * @description Resets all cached u16 values to zero and marks them as changed, including resetting the length and startIndex.
311
+ */
312
+ @inline
313
+ public reset(): void {
314
+ // Reset the length and startIndex to zero
315
+ this._length = 0;
316
+ this._startIndex = 0;
317
+ this._isChangedLength = true;
318
+ this._isChangedStartIndex = true;
319
+
320
+ this.save();
321
+ }
322
+
323
+ /**
324
+ * @method getLength
325
+ * @description Retrieves the current length of the array.
326
+ * @returns {u64} - The current length.
327
+ */
328
+ @inline
329
+ public getLength(): u64 {
330
+ return this._length;
331
+ }
332
+
333
+ /**
334
+ * @method startingIndex
335
+ * @description Retrieves the current starting index of the array.
336
+ * @returns {u64} - The starting index.
337
+ */
338
+ public startingIndex(): u64 {
339
+ return this._startIndex;
340
+ }
341
+
342
+ /**
343
+ * @method setLength
344
+ * @description Sets the length of the array.
345
+ * @param {u64} newLength - The new length to set.
346
+ */
347
+ public setLength(newLength: u64): void {
348
+ if (newLength > this.MAX_LENGTH) {
349
+ throw new Revert('SetLength operation failed: Length exceeds maximum allowed value.');
350
+ }
351
+
352
+ if (newLength < this._length) {
353
+ // Truncate the array if newLength is smaller
354
+ for (let i: u64 = newLength; i < this._length; i++) {
355
+ this.delete(i);
356
+ }
357
+ }
358
+
359
+ this._length = newLength;
360
+ this._isChangedLength = true;
361
+ }
362
+
363
+ /**
364
+ * @private
365
+ * @method ensureValues
366
+ * @description Loads and caches the u16 values from the specified storage slot.
367
+ * @param {u64} slotIndex - The index of the storage slot.
368
+ */
369
+ private ensureValues(slotIndex: u64): void {
370
+ if (!this._isLoaded.has(slotIndex)) {
371
+ const storagePointer = this.calculateStoragePointer(slotIndex);
372
+ const storedU256: u256 = Blockchain.getStorageAt(storagePointer, this.defaultValue);
373
+ const slotValues = this.unpackU256(storedU256);
374
+ this._values.set(slotIndex, slotValues);
375
+ this._isLoaded.add(slotIndex);
376
+ }
377
+ }
378
+
379
+ /**
380
+ * @private
381
+ * @method packValues
382
+ * @description Packs the sixteen cached u16 values into a single u256 for storage.
383
+ * @param {u64} slotIndex - The index of the storage slot.
384
+ * @returns {u256} - The packed u256 value.
385
+ */
386
+ private packValues(slotIndex: u64): u256 {
387
+ const values = this._values.get(slotIndex);
388
+ if (!values) {
389
+ return u256.Zero;
390
+ }
391
+ const packed = new u256();
392
+
393
+ // Pack values[0..3] into lo1
394
+ packed.lo1 =
395
+ (u64(values[0]) << 48) |
396
+ (u64(values[1]) << 32) |
397
+ (u64(values[2]) << 16) |
398
+ u64(values[3]);
399
+
400
+ // Pack values[4..7] into lo2
401
+ packed.lo2 =
402
+ (u64(values[4]) << 48) |
403
+ (u64(values[5]) << 32) |
404
+ (u64(values[6]) << 16) |
405
+ u64(values[7]);
406
+
407
+ // Pack values[8..11] into hi1
408
+ packed.hi1 =
409
+ (u64(values[8]) << 48) |
410
+ (u64(values[9]) << 32) |
411
+ (u64(values[10]) << 16) |
412
+ u64(values[11]);
413
+
414
+ // Pack values[12..15] into hi2
415
+ packed.hi2 =
416
+ (u64(values[12]) << 48) |
417
+ (u64(values[13]) << 32) |
418
+ (u64(values[14]) << 16) |
419
+ u64(values[15]);
420
+
421
+ return packed;
422
+ }
423
+
424
+ /**
425
+ * @private
426
+ * @method unpackU256
427
+ * @description Unpacks a u256 value into an array of sixteen u16s.
428
+ * @param {u256} storedU256 - The u256 value to unpack.
429
+ * @returns {u16[]} - An array of sixteen u16 values.
430
+ */
431
+ private unpackU256(storedU256: u256): u16[] {
432
+ const values: u16[] = new Array<u16>(16);
433
+
434
+ // Unpack lo1 into values[0..3]
435
+ values[0] = u16(storedU256.lo1 >> 48);
436
+ values[1] = u16((storedU256.lo1 >> 32) & 0xffff);
437
+ values[2] = u16((storedU256.lo1 >> 16) & 0xffff);
438
+ values[3] = u16(storedU256.lo1 & 0xffff);
439
+
440
+ // Unpack lo2 into values[4..7]
441
+ values[4] = u16(storedU256.lo2 >> 48);
442
+ values[5] = u16((storedU256.lo2 >> 32) & 0xffff);
443
+ values[6] = u16((storedU256.lo2 >> 16) & 0xffff);
444
+ values[7] = u16(storedU256.lo2 & 0xffff);
445
+
446
+ // Unpack hi1 into values[8..11]
447
+ values[8] = u16(storedU256.hi1 >> 48);
448
+ values[9] = u16((storedU256.hi1 >> 32) & 0xffff);
449
+ values[10] = u16((storedU256.hi1 >> 16) & 0xffff);
450
+ values[11] = u16(storedU256.hi1 & 0xffff);
451
+
452
+ // Unpack hi2 into values[12..15]
453
+ values[12] = u16(storedU256.hi2 >> 48);
454
+ values[13] = u16((storedU256.hi2 >> 32) & 0xffff);
455
+ values[14] = u16((storedU256.hi2 >> 16) & 0xffff);
456
+ values[15] = u16(storedU256.hi2 & 0xffff);
457
+
458
+ return values;
459
+ }
460
+
461
+ /**
462
+ * @private
463
+ * @method calculateStoragePointer
464
+ * @description Calculates the storage pointer for a given slot index by incrementing the base pointer.
465
+ * @param {u64} slotIndex - The index of the storage slot.
466
+ * @returns {u256} - The calculated storage pointer.
467
+ */
468
+ private calculateStoragePointer(slotIndex: u64): u256 {
469
+ // Each slot is identified by baseU256Pointer + slotIndex + 1
470
+ return SafeMath.add(this.baseU256Pointer, u256.fromU64(slotIndex + 1));
471
+ }
472
+ }