@btc-vision/btc-runtime 1.3.9 → 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.
@@ -1,136 +1,229 @@
1
- import { MemorySlotPointer } from '../memory/MemorySlotPointer';
1
+ import { u256 } from 'as-bignum/assembly';
2
2
  import { Blockchain } from '../env';
3
- import { encodePointer } from '../math/abi';
4
3
  import { BytesWriter } from '../buffer/BytesWriter';
5
- import { u256 } from 'as-bignum/assembly';
6
4
  import { SafeMath } from '../types/SafeMath';
5
+ import { Revert } from '../types/Revert';
7
6
 
8
7
  /**
9
8
  * @class StoredU256Array
10
- * @description Manages an array of u256 values across multiple u256 storage slots. Each u256 slot holds one u256 value.
9
+ * @description Manages an array of u256 values across multiple storage slots. Each slot holds one u256 value.
11
10
  */
12
11
  @final
13
12
  export class StoredU256Array {
14
13
  private readonly baseU256Pointer: u256;
15
14
  private readonly lengthPointer: u256;
16
15
 
17
- // Internal cache for multiple storage slots, each holding one u256 value
18
- private _values: Array<u256> = []; // Each element is a u256 value
19
- private _isLoaded: Array<bool> = []; // Indicates if the corresponding slot is loaded
20
- private _isChanged: Array<bool> = []; // Indicates if the corresponding slot has been modified
16
+ // Internal cache for storage slots
17
+ private _values: Map<u64, u256> = new Map(); // Map from slotIndex to u256 value
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
21
20
 
22
- // Internal variables for length management
23
- private _length: u16 = 0; // Current length of the array
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
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;
25
29
 
26
30
  /**
27
31
  * @constructor
28
32
  * @param {u16} pointer - The primary pointer identifier.
29
- * @param {MemorySlotPointer} subPointer - The sub-pointer for memory slot addressing.
33
+ * @param {Uint8Array} subPointer - The sub-pointer for memory slot addressing.
30
34
  * @param {u256} defaultValue - The default u256 value if storage is uninitialized.
31
35
  */
32
36
  constructor(
33
37
  public pointer: u16,
34
- public subPointer: MemorySlotPointer,
38
+ public subPointer: Uint8Array,
35
39
  private defaultValue: u256,
36
40
  ) {
37
41
  // Initialize the base u256 pointer using the primary pointer and subPointer
38
42
  const writer = new BytesWriter(32);
39
- writer.writeU256(subPointer);
40
- this.baseU256Pointer = encodePointer(pointer, writer.getBuffer());
43
+ writer.writeU16(pointer);
44
+ writer.writeBytes(subPointer);
41
45
 
42
- // Initialize the length pointer by incrementing the base pointer by 1
43
- this.lengthPointer = SafeMath.add(this.baseU256Pointer, u256.One);
46
+ // Initialize the base and length pointers
47
+ const baseU256Pointer = u256.fromBytes(writer.getBuffer(), true);
48
+ const lengthPointer = baseU256Pointer.clone();
44
49
 
45
- // Load the current length from storage
46
- const storedLength: u256 = Blockchain.getStorageAt(this.lengthPointer, u256.Zero);
47
- this._length = u16(storedLength.toU32());
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;
48
54
 
49
- // Optionally, you can preload values based on the initial length
50
- for (let i: u32 = 0; i < this._length; i++) {
51
- this.ensureValues(i);
52
- }
55
+ this._length = storedLengthAndStartIndex.lo1; // Bytes 0-7: length
56
+ this._startIndex = storedLengthAndStartIndex.lo2; // Bytes 8-15: startIndex
53
57
  }
54
58
 
55
59
  /**
56
60
  * @method get
57
61
  * @description Retrieves the u256 value at the specified global index.
58
- * @param {u32} index - The global index (0 to ∞) of the u256 value to retrieve.
62
+ * @param {u64} index - The global index (0 to ∞) of the u256 value to retrieve.
59
63
  * @returns {u256} - The u256 value at the specified index.
60
64
  */
61
65
  @inline
62
- public get(index: u32): u256 {
63
- const slotIndex: u32 = index; // Each slot holds one u256
66
+ public get(index: u64): u256 {
67
+ assert(index < this._length, 'Index out of bounds');
68
+ const slotIndex: u32 = <u32>index;
64
69
  this.ensureValues(slotIndex);
65
- return this._values[slotIndex];
70
+ const value = this._values.get(slotIndex);
71
+ return value ? value : u256.Zero;
66
72
  }
67
73
 
68
74
  /**
69
75
  * @method set
70
76
  * @description Sets the u256 value at the specified global index.
71
- * @param {u32} index - The global index (0 to ∞) of the u256 value to set.
77
+ * @param {u64} index - The global index (0 to ∞) of the u256 value to set.
72
78
  * @param {u256} value - The u256 value to assign.
73
79
  */
74
80
  @inline
75
- public set(index: u32, value: u256): void {
76
- const slotIndex: u32 = index;
81
+ public set(index: u64, value: u256): void {
82
+ assert(index < this._length, 'Index exceeds current array length');
83
+ const slotIndex: u32 = <u32>index;
77
84
  this.ensureValues(slotIndex);
78
85
 
79
- if (!u256.eq(this._values[slotIndex], value)) {
80
- this._values[slotIndex] = value;
81
- this._isChanged[slotIndex] = true;
86
+ const currentValue = this._values.get(slotIndex);
87
+ if (!u256.eq(currentValue, value)) {
88
+ this._values.set(slotIndex, value);
89
+ this._isChanged.add(slotIndex);
90
+ }
91
+ }
82
92
 
83
- // Update length if setting a new index beyond current length
84
- if (index >= this._length) {
85
- this._length = u16(index + 1);
86
- this._isChangedLength = true;
87
- }
93
+ /**
94
+ * @method push
95
+ * @description Appends a new u256 value to the end of the array.
96
+ * @param {u256} value - The u256 value to append.
97
+ */
98
+ public push(value: u256): void {
99
+ if (this._length >= this.MAX_LENGTH) {
100
+ throw new Revert(
101
+ 'Push operation failed: Array has reached its maximum allowed length.',
102
+ );
103
+ }
104
+
105
+ const newIndex: u64 = this._length;
106
+ const effectiveIndex: u64 = this._startIndex + newIndex;
107
+ const wrappedIndex: u64 =
108
+ effectiveIndex < this.MAX_LENGTH ? effectiveIndex : effectiveIndex % this.MAX_LENGTH;
109
+ const slotIndex: u32 = <u32>wrappedIndex;
110
+
111
+ // Ensure the slot is loaded
112
+ this.ensureValues(slotIndex);
113
+
114
+ // Set the new value
115
+ this._values.set(slotIndex, value);
116
+ this._isChanged.add(slotIndex);
117
+
118
+ // Increment the length
119
+ this._length += 1;
120
+ this._isChangedLength = true;
121
+ }
122
+
123
+ /**
124
+ * @method delete
125
+ * @description Deletes the u256 value at the specified index by setting it to zero. Does not reorder the array.
126
+ * @param {u64} index - The global index of the u256 value to delete.
127
+ */
128
+ public delete(index: u64): void {
129
+ if (index >= this._length) {
130
+ throw new Revert('Delete operation failed: Index out of bounds.');
131
+ }
132
+
133
+ const slotIndex: u32 = <u32>index;
134
+ this.ensureValues(slotIndex);
135
+
136
+ const currentValue = this._values.get(slotIndex);
137
+ if (!u256.eq(currentValue, u256.Zero)) {
138
+ this._values.set(slotIndex, u256.Zero);
139
+ this._isChanged.add(slotIndex);
88
140
  }
89
141
  }
90
142
 
143
+ /**
144
+ * @method shift
145
+ * @description Removes the first element of the array by setting it to zero, decrementing the length, and incrementing the startIndex.
146
+ * If the startIndex reaches the maximum value of u64, it wraps around to 0.
147
+ */
148
+ public shift(): void {
149
+ if (this._length === 0) {
150
+ throw new Revert('Shift operation failed: Array is empty.');
151
+ }
152
+
153
+ const currentStartIndex: u64 = this._startIndex;
154
+ const slotIndex: u32 = <u32>currentStartIndex;
155
+ this.ensureValues(slotIndex);
156
+
157
+ const currentValue = this._values.get(slotIndex);
158
+ if (!u256.eq(currentValue, u256.Zero)) {
159
+ this._values.set(slotIndex, u256.Zero);
160
+ this._isChanged.add(slotIndex);
161
+ }
162
+
163
+ // Decrement the length
164
+ this._length -= 1;
165
+ this._isChangedLength = true;
166
+
167
+ // Increment the startIndex with wrap-around
168
+ if (this._startIndex < this.MAX_LENGTH - 1) {
169
+ this._startIndex += 1;
170
+ } else {
171
+ this._startIndex = 0;
172
+ }
173
+ this._isChangedStartIndex = true;
174
+ }
175
+
91
176
  /**
92
177
  * @method save
93
- * @description Persists all cached u256 values and the length to their respective storage slots if any have been modified.
178
+ * @description Persists all cached u256 values, the length, and the startIndex to their respective storage slots if any have been modified.
94
179
  */
95
180
  public save(): void {
96
- const max: u32 = this._values.length;
97
-
98
- for (let slotIndex: u32 = 0; slotIndex < max; slotIndex++) {
99
- if (this._isChanged[slotIndex]) {
100
- const packed = this.packValues(slotIndex);
101
- const storagePointer = this.calculateStoragePointer(slotIndex);
102
- Blockchain.setStorageAt(storagePointer, packed);
103
- this._isChanged[slotIndex] = false;
104
- }
181
+ // Save all changed slots
182
+ const changed = this._isChanged.values();
183
+ for (let i = 0; i < changed.length; i++) {
184
+ const slotIndex = changed[i];
185
+ const storagePointer = this.calculateStoragePointer(slotIndex);
186
+ const value = this._values.get(slotIndex);
187
+ Blockchain.setStorageAt(storagePointer, value);
105
188
  }
106
-
107
- if (this._isChangedLength) {
108
- const lengthU256 = u256.from(this._length);
109
- Blockchain.setStorageAt(this.lengthPointer, lengthU256);
189
+ this._isChanged.clear();
190
+
191
+ // Save length and startIndex if changed
192
+ if (this._isChangedLength || this._isChangedStartIndex) {
193
+ const packedLengthAndStartIndex = new u256();
194
+ packedLengthAndStartIndex.lo1 = this._length;
195
+ packedLengthAndStartIndex.lo2 = this._startIndex;
196
+ Blockchain.setStorageAt(this.lengthPointer, packedLengthAndStartIndex);
110
197
  this._isChangedLength = false;
198
+ this._isChangedStartIndex = false;
111
199
  }
112
200
  }
113
201
 
114
202
  /**
115
- * @method delete
116
- * @description Deletes all storage slots by setting them to zero, including the length slot.
203
+ * @method deleteAll
204
+ * @description Deletes all storage slots by setting them to zero, including the length and startIndex slots.
117
205
  */
118
- public delete(): void {
119
- const max: u32 = this._values.length;
120
- for (let slotIndex: u32 = 0; slotIndex < max; slotIndex++) {
206
+ public deleteAll(): void {
207
+ // Iterate over all loaded slots and clear them
208
+ const keys = this._values.keys();
209
+ for (let i = 0; i < keys.length; i++) {
210
+ const slotIndex = keys[i];
121
211
  const storagePointer = this.calculateStoragePointer(slotIndex);
122
212
  Blockchain.setStorageAt(storagePointer, u256.Zero);
123
213
  }
124
214
 
125
- // Reset the length to zero
126
- Blockchain.setStorageAt(this.lengthPointer, u256.Zero);
215
+ // Reset the length and startIndex to zero
216
+ const zeroLengthAndStartIndex = u256.Zero;
217
+ Blockchain.setStorageAt(this.lengthPointer, zeroLengthAndStartIndex);
127
218
  this._length = 0;
219
+ this._startIndex = 0;
128
220
  this._isChangedLength = false;
221
+ this._isChangedStartIndex = false;
129
222
 
130
223
  // Clear internal caches
131
- this._values = [];
132
- this._isLoaded = [];
133
- this._isChanged = [];
224
+ this._values.clear();
225
+ this._isLoaded.clear();
226
+ this._isChanged.clear();
134
227
  }
135
228
 
136
229
  /**
@@ -142,7 +235,7 @@ export class StoredU256Array {
142
235
  @inline
143
236
  public setMultiple(startIndex: u32, values: u256[]): void {
144
237
  for (let i: u32 = 0; i < values.length; i++) {
145
- this.set(startIndex + i, values[i]);
238
+ this.set(<u64>(startIndex + i), values[i]);
146
239
  }
147
240
  }
148
241
 
@@ -155,9 +248,10 @@ export class StoredU256Array {
155
248
  */
156
249
  @inline
157
250
  public getAll(startIndex: u32, count: u32): u256[] {
251
+ assert(startIndex + count <= this._length, 'Requested range exceeds array length');
158
252
  const result: u256[] = new Array<u256>(count);
159
253
  for (let i: u32 = 0; i < count; i++) {
160
- result[i] = this.get(startIndex + i);
254
+ result[i] = this.get(<u64>(startIndex + i));
161
255
  }
162
256
  return result;
163
257
  }
@@ -170,10 +264,10 @@ export class StoredU256Array {
170
264
  @inline
171
265
  public toString(): string {
172
266
  let str = '[';
173
- for (let slotIndex: u32 = 0; slotIndex < this._values.length; slotIndex++) {
174
- const value = this._values[slotIndex];
267
+ for (let i: u32 = 0; i < this._length; i++) {
268
+ const value = this.get(<u64>i);
175
269
  str += value.toString();
176
- if (slotIndex !== this._values.length - 1) {
270
+ if (i !== this._length - 1) {
177
271
  str += ', ';
178
272
  }
179
273
  }
@@ -189,11 +283,14 @@ export class StoredU256Array {
189
283
  @inline
190
284
  public toBytes(): u8[] {
191
285
  const bytes: u8[] = new Array<u8>();
192
- for (let slotIndex: u32 = 0; slotIndex < this._values.length; slotIndex++) {
193
- const packed = this.packValues(slotIndex);
194
- const slotBytes = packed.toBytes();
195
- for (let i: u32 = 0; i < slotBytes.length; i++) {
196
- bytes.push(slotBytes[i]);
286
+ for (let i: u32 = 0; i < this._length; i++) {
287
+ this.ensureValues(i);
288
+ const value = this._values.get(i);
289
+ if (value) {
290
+ const valueBytes = value.toBytes();
291
+ for (let j: u32 = 0; j < valueBytes.length; j++) {
292
+ bytes.push(valueBytes[j]);
293
+ }
197
294
  }
198
295
  }
199
296
  return bytes;
@@ -201,38 +298,53 @@ export class StoredU256Array {
201
298
 
202
299
  /**
203
300
  * @method reset
204
- * @description Resets all cached u256 values to zero and marks them as changed, including resetting the length.
301
+ * @description Resets all cached u256 values to zero and marks them as changed, including resetting the length and startIndex.
205
302
  */
206
303
  @inline
207
304
  public reset(): void {
208
- for (let slotIndex: u32 = 0; slotIndex < this._values.length; slotIndex++) {
209
- this._values[slotIndex] = u256.Zero;
210
- this._isChanged[slotIndex] = true;
211
- }
212
-
213
- // Reset the length to zero
305
+ // Reset the length and startIndex to zero
214
306
  this._length = 0;
307
+ this._startIndex = 0;
215
308
  this._isChangedLength = true;
309
+ this._isChangedStartIndex = true;
310
+
311
+ this.save();
216
312
  }
217
313
 
218
314
  /**
219
315
  * @method getLength
220
316
  * @description Retrieves the current length of the array.
221
- * @returns {u16} - The current length.
317
+ * @returns {u64} - The current length.
222
318
  */
223
319
  @inline
224
- public getLength(): u16 {
320
+ public getLength(): u64 {
225
321
  return this._length;
226
322
  }
227
323
 
324
+ /**
325
+ * @method startingIndex
326
+ * @description Retrieves the current starting index of the array.
327
+ * @returns {u64} - The starting index.
328
+ */
329
+ public startingIndex(): u64 {
330
+ return this._startIndex;
331
+ }
332
+
228
333
  /**
229
334
  * @method setLength
230
335
  * @description Sets the length of the array.
231
- * @param {u16} newLength - The new length to set.
336
+ * @param {u64} newLength - The new length to set.
232
337
  */
233
- public setLength(newLength: u16): void {
234
- if (newLength > u16.MAX_VALUE) {
235
- throw new Error('Length exceeds maximum allowed value.');
338
+ public setLength(newLength: u64): void {
339
+ if (newLength > this.MAX_LENGTH) {
340
+ throw new Revert('SetLength operation failed: Length exceeds maximum allowed value.');
341
+ }
342
+
343
+ if (newLength < this._length) {
344
+ // Truncate the array if newLength is smaller
345
+ for (let i: u64 = newLength; i < this._length; i++) {
346
+ this.delete(i);
347
+ }
236
348
  }
237
349
 
238
350
  this._length = newLength;
@@ -246,55 +358,25 @@ export class StoredU256Array {
246
358
  * @param {u32} slotIndex - The index of the storage slot.
247
359
  */
248
360
  private ensureValues(slotIndex: u32): void {
249
- // Initialize arrays if necessary
250
- while (slotIndex >= <u32>this._isLoaded.length) {
251
- this._isLoaded.push(false);
252
- this._isChanged.push(false);
253
- this._values.push(u256.Zero);
254
- }
255
-
256
- if (!this._isLoaded[slotIndex]) {
361
+ if (!this._isLoaded.has(slotIndex)) {
257
362
  const storagePointer = this.calculateStoragePointer(slotIndex);
258
363
  const storedU256: u256 = Blockchain.getStorageAt(storagePointer, this.defaultValue);
259
- this._values[slotIndex] = this.unpackU256(storedU256);
260
- this._isLoaded[slotIndex] = true;
364
+ this._values.set(slotIndex, storedU256);
365
+ this._isLoaded.add(slotIndex);
261
366
  }
262
367
  }
263
368
 
264
- /**
265
- * @private
266
- * @method packValues
267
- * @description Retrieves the cached u256 value for storage.
268
- * @param {u32} slotIndex - The index of the storage slot.
269
- * @returns {u256} - The packed u256 value.
270
- */
271
- private packValues(slotIndex: u32): u256 {
272
- return this._values[slotIndex];
273
- }
274
-
275
- /**
276
- * @private
277
- * @method unpackU256
278
- * @description Returns the u256 value as is since no unpacking is required.
279
- * @param {u256} storedU256 - The u256 value to unpack.
280
- * @returns {u256} - The unpacked u256 value.
281
- */
282
- private unpackU256(storedU256: u256): u256 {
283
- return storedU256;
284
- }
285
-
286
369
  /**
287
370
  * @private
288
371
  * @method calculateStoragePointer
289
- * @description Calculates the storage pointer for a given slot index by incrementing the subpointer.
372
+ * @description Calculates the storage pointer for a given slot index by incrementing the base pointer.
290
373
  * @param {u32} slotIndex - The index of the storage slot.
291
374
  * @returns {u256} - The calculated storage pointer.
292
375
  */
293
- private calculateStoragePointer(slotIndex: u32): u256 {
294
- // Clone the subpointer to avoid mutating the original
295
- const modifiedSubPointer = this.subPointer.clone();
296
-
297
- // Increment the subpointer by slotIndex using SafeMath
298
- return SafeMath.add(modifiedSubPointer, u256.fromU32(slotIndex));
376
+ private calculateStoragePointer(slotIndex: u64): u256 {
377
+ // Each slot is identified by baseU256Pointer + slotIndex + 1
378
+ // Slot 0: baseU256Pointer + 1 (first element)
379
+ // Slot 1: baseU256Pointer + 2, etc.
380
+ return SafeMath.add(this.baseU256Pointer, u256.fromU64(slotIndex + 1));
299
381
  }
300
382
  }