@btc-vision/btc-runtime 1.6.1 → 1.6.3

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,178 @@
1
+ import { u256 } from '@btc-vision/as-bignum/assembly';
2
+ import { BytesWriter } from '../buffer/BytesWriter';
3
+ import { Blockchain } from '../env';
4
+ import { encodePointer } from '../math/abi';
5
+ import { BytesReader } from '../buffer/BytesReader';
6
+
7
+ /**
8
+ * @class StoredU32
9
+ * @description Manages up to height u32 values within a single u256 storage slot.
10
+ */
11
+ @final
12
+ export class StoredU32 {
13
+ private readonly bufferPointer: Uint8Array;
14
+
15
+ // Internal cache for four u32 values
16
+ private _values: u32[] = [0, 0, 0, 0, 0, 0, 0, 0];
17
+
18
+ // Flag to indicate if values are loaded from storage
19
+ private isLoaded: bool = false;
20
+
21
+ // Flag to indicate if any value has been changed
22
+ private isChanged: bool = false;
23
+
24
+ /**
25
+ * @constructor
26
+ * @param {u16} pointer - The primary pointer identifier.
27
+ * @param {Uint8Array} subPointer - The sub-pointer for memory slot addressing.
28
+ */
29
+ constructor(
30
+ public pointer: u16,
31
+ public subPointer: Uint8Array,
32
+ ) {
33
+ assert(
34
+ subPointer.length <= 30,
35
+ `You must pass a 30 bytes sub-pointer. (StoredU32, got ${subPointer.length})`,
36
+ );
37
+
38
+ this.bufferPointer = encodePointer(pointer, subPointer);
39
+ }
40
+
41
+ /**
42
+ * @method get
43
+ * @description Retrieves the u32 value at the specified offset.
44
+ * @param {u8} index - The index (0 to 7) of the u32 value to retrieve.
45
+ * @returns {u32} - The u32 value at the specified index.
46
+ */
47
+ @inline
48
+ public get(index: u8): u32 {
49
+ assert(index < 8, 'Index out of bounds for StoredU32 (0-7)');
50
+ this.ensureValues();
51
+ return this._values[index];
52
+ }
53
+
54
+ /**
55
+ * @method set
56
+ * @description Sets the u32 value at the specified offset.
57
+ * @param {u8} index - The index (0 to 7) of the u32 value to set.
58
+ * @param {u32} value - The u32 value to assign.
59
+ */
60
+ @inline
61
+ public set(index: u8, value: u32): void {
62
+ assert(index < 8, 'Index out of bounds for StoredU32 (0-7)');
63
+ this.ensureValues();
64
+ if (this._values[index] != value) {
65
+ this._values[index] = value;
66
+ this.isChanged = true;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * @method save
72
+ * @description Persists the cached u32 values to storage if any have been modified.
73
+ */
74
+ public save(): void {
75
+ if (this.isChanged) {
76
+ const packed = this.packValues();
77
+ Blockchain.setStorageAt(this.bufferPointer, packed);
78
+ this.isChanged = false;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * @method setMultiple
84
+ * @description Sets multiple u32 values at once.
85
+ * @param {[u32, u32, u32, u32,u32, u32, u32, u32]} values - An array of four u32 values to set.
86
+ */
87
+ @inline
88
+ public setMultiple(values: u32[]): void {
89
+ this.ensureValues();
90
+ let changed = false;
91
+ for (let i: u8 = 0; i < 8; i++) {
92
+ if (this._values[i] != values[i]) {
93
+ this._values[i] = values[i];
94
+ changed = true;
95
+ }
96
+ }
97
+ if (changed) {
98
+ this.isChanged = true;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * @method getAll
104
+ * @description Retrieves all height u32 values as a tuple.
105
+ * @returns {[u32, u32, u32, u32,u32, u32, u32, u32]} - A tuple containing all four u32 values.
106
+ */
107
+ @inline
108
+ public getAll(): u32[] {
109
+ this.ensureValues();
110
+ return this._values;
111
+ }
112
+
113
+ /**
114
+ * @method toString
115
+ * @description Returns a string representation of all height u32 values.
116
+ * @returns {string} - A string in the format "[value0, value1, value2, value3, value4, value5, value6, value7]".
117
+ */
118
+ @inline
119
+ public toString(): string {
120
+ this.ensureValues();
121
+ return `[${this._values[0].toString()}, ${this._values[1].toString()}, ${this._values[2].toString()}, ${this._values[3].toString()},, ${this._values[4].toString()},, ${this._values[5].toString()},, ${this._values[6].toString()},, ${this._values[7].toString()}]`;
122
+ }
123
+
124
+ /**
125
+ * @method reset
126
+ * @description Resets the cached values to default and marks as changed.
127
+ */
128
+ @inline
129
+ public reset(): void {
130
+ this._values = [0, 0, 0, 0, 0, 0, 0, 0];
131
+ this.isChanged = true;
132
+ }
133
+
134
+ /**
135
+ * @private
136
+ * @method ensureValues
137
+ * @description Loads and unpacks the u256 value from storage into height u32 cache variables.
138
+ */
139
+ private ensureValues(): void {
140
+ if (!this.isLoaded) {
141
+ const storedU256: Uint8Array = Blockchain.getStorageAt(this.bufferPointer);
142
+
143
+ const reader = new BytesReader(storedU256);
144
+
145
+ this._values[0] = reader.readU32();
146
+ this._values[1] = reader.readU32();
147
+ this._values[2] = reader.readU32();
148
+ this._values[3] = reader.readU32();
149
+ this._values[4] = reader.readU32();
150
+ this._values[5] = reader.readU32();
151
+ this._values[6] = reader.readU32();
152
+ this._values[7] = reader.readU32();
153
+
154
+ this.isLoaded = true;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * @private
160
+ * @method packValues
161
+ * @description Packs the height cached u32 values into a single u256 for storage.
162
+ * @returns {u256} - The packed u256 value.
163
+ */
164
+ private packValues(): Uint8Array {
165
+ const writer = new BytesWriter(32);
166
+
167
+ writer.writeU32(this._values[0]);
168
+ writer.writeU32(this._values[1]);
169
+ writer.writeU32(this._values[2]);
170
+ writer.writeU32(this._values[3]);
171
+ writer.writeU32(this._values[4]);
172
+ writer.writeU32(this._values[5]);
173
+ writer.writeU32(this._values[6]);
174
+ writer.writeU32(this._values[7]);
175
+
176
+ return writer.getBuffer();
177
+ }
178
+ }
@@ -1,298 +1,39 @@
1
- import { BytesWriter } from '../../buffer/BytesWriter';
2
- import { Blockchain } from '../../env';
3
- import {
4
- addUint8ArraysBE,
5
- bigEndianAdd,
6
- encodeBasePointer,
7
- readLengthAndStartIndex,
8
- u64ToBE32Bytes,
9
- } from '../../math/bytes';
1
+ import { DEFAULT_MAX_LENGTH, StoredPackedArray } from './StoredPackedArray';
2
+ import { bigEndianAdd } from '../../math/bytes';
10
3
  import { Address } from '../../types/Address';
11
- import { Revert } from '../../types/Revert';
12
4
 
13
5
  /**
14
- * @class StoredAddressArray
15
- * @description Manages an array of Address values across multiple storage slots.
16
- * Each slot holds one Address (stored as a raw Uint8Array in storage).
6
+ * StoredAddressArray
7
+ *
8
+ * Array of addresses.
17
9
  */
18
10
  @final
19
- export class StoredAddressArray {
20
- private readonly baseU256Pointer: Uint8Array;
21
- private readonly lengthPointer: Uint8Array;
22
-
23
- private _values: Map<u64, Address> = new Map(); // slotIndex -> Address
24
- private _isChanged: Set<u64> = new Set(); // track changed slotIndexes
25
-
26
- private _length: u64 = 0;
27
- private _startIndex: u64 = 0;
28
- private _isChangedLength: bool = false;
29
- private _isChangedStartIndex: bool = false;
30
-
31
- private readonly MAX_LENGTH: u64 = u64(u32.MAX_VALUE - 1);
32
-
33
- private readonly defaultValue: Address = Address.zero();
34
-
35
- /**
36
- * @constructor
37
- * @param {u16} pointer - The primary pointer identifier.
38
- * @param {Uint8Array} subPointer - The sub-pointer for memory slot addressing.
39
- */
40
- constructor(public pointer: u16, public subPointer: Uint8Array) {
41
- assert(
42
- subPointer.length <= 30,
43
- `You must pass a 30 bytes sub-pointer. (AddressArray, got ${subPointer.length})`,
44
- );
45
-
46
- const basePointer = encodeBasePointer(pointer, subPointer);
47
- this.lengthPointer = Uint8Array.wrap(basePointer.buffer);
48
- this.baseU256Pointer = bigEndianAdd(basePointer, 1);
49
-
50
- const storedLenStart = Blockchain.getStorageAt(basePointer);
51
- const data = readLengthAndStartIndex(storedLenStart);
52
-
53
- this._length = data[0];
54
- this._startIndex = data[1];
55
- }
56
-
57
- @inline
58
- public has(index: u64): bool {
59
- return index < this._length;
60
- }
61
-
62
- /** Get an element by its global index. */
63
- @inline
64
- @operator('[]')
65
- public get(index: u64): Address {
66
- if (index >= this._length) {
67
- throw new Revert('get: index out of range (address array)');
68
- }
69
-
70
- const physicalIndex = (this._startIndex + index) % this.MAX_LENGTH;
71
- const slotIndex: u32 = <u32>physicalIndex;
72
- this.ensureValues(slotIndex);
73
-
74
- return this._values.get(slotIndex);
11
+ export class StoredAddressArray extends StoredPackedArray<Address> {
12
+ public constructor(pointer: u16, subPointer: Uint8Array, maxLength: u32 = DEFAULT_MAX_LENGTH) {
13
+ super(pointer, subPointer, Address.zero(), maxLength);
75
14
  }
76
15
 
77
- /** Set an element by its global index. */
78
- @inline
79
- @operator('[]=')
80
- public set(index: u64, value: Address): void {
81
- if (index >= this._length) {
82
- throw new Revert('set: index out of range (address array)');
83
- }
84
-
85
- const physicalIndex = (this._startIndex + index) % this.MAX_LENGTH;
86
- const slotIndex: u32 = <u32>physicalIndex;
87
- this.ensureValues(slotIndex);
88
-
89
- const currentValue = this._values.get(slotIndex);
90
- if (currentValue != value) {
91
- this._values.set(slotIndex, value);
92
- this._isChanged.add(slotIndex);
93
- }
16
+ protected getSlotCapacity(): u32 {
17
+ return 1; // 1 x u256 => 32 bytes
94
18
  }
95
19
 
96
- /** Append an address at the end of the array. */
97
- @inline
98
- public push(value: Address): void {
99
- if (this._length >= this.MAX_LENGTH) {
100
- throw new Revert('push: array reached maximum length (address array)');
101
- }
102
-
103
- const newLogicalIndex: u64 = this._length;
104
- const physicalIndex: u64 = (this._startIndex + newLogicalIndex) % this.MAX_LENGTH;
105
- const slotIndex: u32 = <u32>physicalIndex;
106
-
107
- this.ensureValues(slotIndex);
108
- this._values.set(slotIndex, value);
109
- this._isChanged.add(slotIndex);
110
-
111
- this._length += 1;
112
- this._isChangedLength = true;
20
+ protected zeroValue(): Address {
21
+ return Address.zero();
113
22
  }
114
23
 
115
- /** Delete the last element. */
116
- public deleteLast(): void {
117
- if (this._length === 0) {
118
- throw new Revert('deleteLast: array is empty (address array)');
119
- }
120
-
121
- const lastLogicalIndex: u64 = this._length - 1;
122
- const physicalIndex: u64 = (this._startIndex + lastLogicalIndex) % this.MAX_LENGTH;
123
- const slotIndex: u32 = <u32>physicalIndex;
124
- this.ensureValues(slotIndex);
125
-
126
- const currentValue = this._values.get(slotIndex);
127
- if (currentValue != this.defaultValue) {
128
- this._values.set(slotIndex, this.defaultValue);
129
- this._isChanged.add(slotIndex);
130
- }
131
-
132
- this._length -= 1;
133
- this._isChangedLength = true;
24
+ protected eq(a: Address, b: Address): bool {
25
+ return a == b;
134
26
  }
135
27
 
136
- /** Adjust the starting index. */
137
- public setStartingIndex(index: u64): void {
138
- this._startIndex = index;
139
- this._isChangedStartIndex = true;
28
+ protected packSlot(values: Address[]): Uint8Array {
29
+ return values[0];
140
30
  }
141
31
 
142
- /** Delete a specific element by setting it to `defaultValue`. */
143
- @inline
144
- public delete(index: u64): void {
145
- if (index > this.MAX_LENGTH) {
146
- throw new Revert('delete: index out of range (address array)');
147
- }
148
-
149
- const physicalIndex: u64 = (this._startIndex + index) % this.MAX_LENGTH;
150
- const slotIndex: u32 = <u32>physicalIndex;
151
- this.ensureValues(slotIndex);
152
-
153
- const currentValue = this._values.get(slotIndex);
154
- if (currentValue != this.defaultValue) {
155
- this._values.set(slotIndex, this.defaultValue);
156
- this._isChanged.add(slotIndex);
157
- }
32
+ protected unpackSlot(slotData: Uint8Array): Address[] {
33
+ return [Address.fromUint8Array(slotData)];
158
34
  }
159
35
 
160
- /**
161
- * Persist changes to storage.
162
- * - Store any changed slotIndex -> Address
163
- * - Store updated length and startIndex if changed
164
- */
165
- @inline
166
- public save(): void {
167
- // 1) Save changed slots
168
- const changed = this._isChanged.values();
169
- for (let i = 0; i < changed.length; i++) {
170
- const slotIndex = changed[i];
171
- const storagePointer = this.calculateStoragePointer(slotIndex);
172
-
173
- const value = this._values.get(slotIndex);
174
- Blockchain.setStorageAt(storagePointer, value);
175
- }
176
- this._isChanged.clear();
177
-
178
- // 2) Save length and startIndex if changed
179
- if (this._isChangedLength || this._isChangedStartIndex) {
180
- const writer = new BytesWriter(16);
181
- writer.writeU64(this._length);
182
- writer.writeU64(this._startIndex);
183
-
184
- Blockchain.setStorageAt(this.lengthPointer, writer.getBuffer());
185
- this._isChangedLength = false;
186
- this._isChangedStartIndex = false;
187
- }
188
- }
189
-
190
- /** Clear entire array content from storage, reset length and startIndex. */
191
- public deleteAll(): void {
192
- const keys = this._values.keys();
193
- for (let i = 0; i < keys.length; i++) {
194
- const slotIndex = keys[i];
195
- const storagePointer = this.calculateStoragePointer(slotIndex);
196
- Blockchain.setStorageAt(storagePointer, this.defaultValue);
197
- }
198
-
199
- Blockchain.setStorageAt(this.lengthPointer, new Uint8Array(32));
200
-
201
- this._length = 0;
202
- this._startIndex = 0;
203
- this._isChangedLength = false;
204
- this._isChangedStartIndex = false;
205
-
206
- this._values.clear();
207
- this._isChanged.clear();
208
- }
209
-
210
- /** Bulk-set multiple addresses starting at `startIndex`. */
211
- @inline
212
- public setMultiple(startIndex: u32, values: Address[]): void {
213
- for (let i: u32 = 0; i < values.length; i++) {
214
- this.set(<u64>(startIndex + i), values[i]);
215
- }
216
- }
217
-
218
- /** Retrieve a batch of addresses (range). */
219
- @inline
220
- public getAll(startIndex: u32, count: u32): Address[] {
221
- if (startIndex + count > this._length) {
222
- throw new Revert('getAll: index out of range (address array)');
223
- }
224
-
225
- const result = new Array<Address>(count);
226
- for (let i: u32 = 0; i < count; i++) {
227
- result[i] = this.get(<u64>(startIndex + i));
228
- }
229
- return result;
230
- }
231
-
232
- /** Returns a string of the form "[addr0, addr1, ...]". */
233
- @inline
234
- public toString(): string {
235
- let str = '[';
236
- for (let i: u32 = 0; i < this._length; i++) {
237
- const value = this.get(<u64>i);
238
- str += value.toString();
239
- if (i !== this._length - 1) {
240
- str += ', ';
241
- }
242
- }
243
- str += ']';
244
- return str;
245
- }
246
-
247
- /** Reset in-memory and persist. */
248
- @inline
249
- public reset(): void {
250
- this._length = 0;
251
- this._startIndex = 0;
252
- this._isChangedLength = true;
253
- this._isChangedStartIndex = true;
254
-
255
- this._values.clear();
256
- this._isChanged.clear();
257
-
258
- this.save();
259
- }
260
-
261
- /** Current array length. */
262
- @inline
263
- public getLength(): u64 {
264
- return this._length;
265
- }
266
-
267
- /** Current starting index. */
268
- public startingIndex(): u64 {
269
- return this._startIndex;
270
- }
271
-
272
- /**
273
- * Ensure the given slot index is loaded into `_values`.
274
- */
275
- private ensureValues(slotIndex: u32): void {
276
- if (!this._values.has(slotIndex)) {
277
- const storagePointer = this.calculateStoragePointer(slotIndex);
278
-
279
- // Load raw bytes from storage
280
- const stored: Uint8Array = Blockchain.getStorageAt(storagePointer);
281
-
282
- const storedAddress: Address =
283
- stored.length == 0 ? this.defaultValue : Address.fromUint8Array(stored);
284
-
285
- this._values.set(slotIndex, storedAddress);
286
- }
287
- }
288
-
289
- /**
290
- * Compute a 32-byte storage pointer = basePointer + (slotIndex + 1) big-endian.
291
- */
292
- private calculateStoragePointer(slotIndex: u64): Uint8Array {
293
- // Convert (slotIndex) to a 32-byte big-endian offset
294
- const offset = u64ToBE32Bytes(slotIndex);
295
-
296
- return addUint8ArraysBE(this.baseU256Pointer, offset);
36
+ protected calculateStoragePointer(slotIndex: u64): Uint8Array {
37
+ return bigEndianAdd(this.basePointer, slotIndex);
297
38
  }
298
39
  }