@btc-vision/btc-runtime 1.3.9 → 1.3.11

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btc-vision/btc-runtime",
3
- "version": "1.3.9",
3
+ "version": "1.3.11",
4
4
  "description": "Bitcoin Smart Contract Runtime",
5
5
  "main": "btc/index.ts",
6
6
  "scripts": {
@@ -49,7 +49,7 @@ export class BytesWriter {
49
49
  public writeAddressArray(value: Address[]): void {
50
50
  if (value.length > 65535) throw new Revert('Array size is too large');
51
51
 
52
- this.writeU16(value.length);
52
+ this.writeU16(u16(value.length));
53
53
 
54
54
  for (let i: i32 = 0; i < value.length; i++) {
55
55
  this.writeAddress(value[i]);
@@ -284,7 +284,6 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
284
284
  protected _transfer(to: Address, value: u256): boolean {
285
285
  const sender = Blockchain.tx.sender;
286
286
 
287
- if (!this.balanceOfMap.has(sender)) throw new Revert();
288
287
  if (this.isSelf(sender)) throw new Revert('Can not transfer from self account');
289
288
 
290
289
  if (u256.eq(value, u256.Zero)) {
@@ -19,6 +19,7 @@ import {
19
19
  nextPointerGreaterThan,
20
20
  storePointer,
21
21
  validateBitcoinAddress,
22
+ verifySchnorrSignature,
22
23
  } from './global';
23
24
  import { DeployContractResponse } from '../interfaces/DeployContractResponse';
24
25
  import { MapU256 } from '../generic/MapU256';
@@ -248,6 +249,22 @@ export class BlockchainEnvironment {
248
249
  return reader.readU256();
249
250
  }
250
251
 
252
+ public verifySchnorrSignature(
253
+ publicKey: Address,
254
+ signature: Uint8Array,
255
+ hash: Uint8Array,
256
+ ): boolean {
257
+ const writer = new BytesWriter(128);
258
+ writer.writeBytes(publicKey);
259
+ writer.writeBytes(signature);
260
+ writer.writeBytes(hash);
261
+
262
+ const result: Uint8Array = verifySchnorrSignature(writer.getBuffer());
263
+
264
+ const reader = new BytesReader(result);
265
+ return reader.readBoolean();
266
+ }
267
+
251
268
  public hasStorageAt(pointerHash: MemorySlotPointer): bool {
252
269
  // We mark zero as the default value for the storage, if something is 0, the storage slot get deleted or is non-existent
253
270
  const val: u256 = this.getStorageAt(pointerHash, u256.Zero);
@@ -53,3 +53,7 @@ export declare function inputs(): Uint8Array;
53
53
  // @ts-ignore
54
54
  @external('env', 'outputs')
55
55
  export declare function outputs(): Uint8Array;
56
+
57
+ // @ts-ignore
58
+ @external('env', 'verifySchnorrSignature')
59
+ export declare function verifySchnorrSignature(data: Uint8Array): Uint8Array;
@@ -1,9 +1,8 @@
1
- import { MemorySlotPointer } from '../memory/MemorySlotPointer';
1
+ import { u128, 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 { u128, 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 StoredU128Array
@@ -12,92 +11,239 @@ import { SafeMath } from '../types/SafeMath';
12
11
  @final
13
12
  export class StoredU128Array {
14
13
  private readonly baseU256Pointer: u256;
14
+ private readonly lengthPointer: u256;
15
+
16
+ // Internal cache for storage slots, each holding two u128 values
17
+ private _values: Map<u64, u128[]> = new Map(); // Map from slotIndex to array of two u128s
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
15
20
 
16
- // Internal cache for multiple storage slots, each holding two u128 values
17
- private _values: Array<u128[]> = []; // Each element is an array of two u128s
18
- private _isLoaded: Array<bool> = []; // Indicates if the corresponding slot is loaded
19
- private _isChanged: Array<bool> = []; // Indicates if the corresponding slot has been modified
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;
20
29
 
21
30
  /**
22
31
  * @constructor
23
32
  * @param {u16} pointer - The primary pointer identifier.
24
- * @param {MemorySlotPointer} subPointer - The sub-pointer for memory slot addressing.
33
+ * @param {Uint8Array} subPointer - The sub-pointer for memory slot addressing.
25
34
  * @param {u256} defaultValue - The default u256 value if storage is uninitialized.
26
35
  */
27
36
  constructor(
28
37
  public pointer: u16,
29
- public subPointer: MemorySlotPointer,
38
+ public subPointer: Uint8Array,
30
39
  private defaultValue: u256,
31
40
  ) {
32
41
  // Initialize the base u256 pointer using the primary pointer and subPointer
33
42
  const writer = new BytesWriter(32);
34
- writer.writeU256(subPointer);
35
- this.baseU256Pointer = encodePointer(pointer, writer.getBuffer());
43
+ writer.writeU16(pointer);
44
+ writer.writeBytes(subPointer);
45
+
46
+ // Initialize the length pointer (slot 0) where both length and startIndex are stored
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
36
57
  }
37
58
 
38
59
  /**
39
60
  * @method get
40
61
  * @description Retrieves the u128 value at the specified global index.
41
- * @param {u32} index - The global index (0 to ∞) of the u128 value to retrieve.
62
+ * @param {u64} index - The global index (0 to ∞) of the u128 value to retrieve.
42
63
  * @returns {u128} - The u128 value at the specified index.
43
64
  */
44
65
  @inline
45
- public get(index: u32): u128 {
46
- const slotIndex: u32 = index / 2; // Each slot holds two u128s
66
+ public get(index: u64): u128 {
67
+ assert(index < this._length, 'Index out of bounds');
68
+ const slotIndex: u32 = <u32>(index / 2); // Each slot holds two u128s
47
69
  const subIndex: u8 = <u8>(index % 2); // 0 or 1
48
70
  this.ensureValues(slotIndex);
49
- return this._values[slotIndex][subIndex];
71
+ const slotValues = this._values.get(slotIndex);
72
+ if (slotValues) {
73
+ return slotValues[subIndex];
74
+ } else {
75
+ return u128.Zero; // Should not happen, as ensureValues should have loaded it
76
+ }
50
77
  }
51
78
 
52
79
  /**
53
80
  * @method set
54
81
  * @description Sets the u128 value at the specified global index.
55
- * @param {u32} index - The global index (0 to ∞) of the u128 value to set.
82
+ * @param {u64} index - The global index (0 to ∞) of the u128 value to set.
56
83
  * @param {u128} value - The u128 value to assign.
57
84
  */
58
85
  @inline
59
- public set(index: u32, value: u128): void {
60
- const slotIndex: u32 = index / 2;
86
+ public set(index: u64, value: u128): void {
87
+ assert(index < this._length, 'Index exceeds current array length');
88
+ const slotIndex: u32 = <u32>(index / 2);
61
89
  const subIndex: u8 = <u8>(index % 2);
62
90
  this.ensureValues(slotIndex);
91
+ const slotValues = this._values.get(slotIndex);
92
+ if (slotValues) {
93
+ if (!u128.eq(slotValues[subIndex], value)) {
94
+ slotValues[subIndex] = value;
95
+ this._isChanged.add(slotIndex);
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * @method push
102
+ * @description Appends a new u128 value to the end of the array.
103
+ * @param {u128} value - The u128 value to append.
104
+ */
105
+ public push(value: u128): void {
106
+ if (this._length >= this.MAX_LENGTH) {
107
+ throw new Revert(
108
+ 'Push operation failed: Array has reached its maximum allowed length.',
109
+ );
110
+ }
111
+
112
+ const newIndex: u64 = this._length;
113
+ const effectiveIndex: u64 = this._startIndex + newIndex;
114
+ const wrappedIndex: u64 =
115
+ effectiveIndex < this.MAX_LENGTH ? effectiveIndex : effectiveIndex % this.MAX_LENGTH;
116
+ const slotIndex: u32 = <u32>(wrappedIndex / 2);
117
+ const subIndex: u8 = <u8>(wrappedIndex % 2);
118
+
119
+ // Ensure the slot is loaded
120
+ this.ensureValues(slotIndex);
121
+
122
+ // Set the new value in the appropriate sub-index
123
+ const slotValues = this._values.get(slotIndex);
124
+ if (slotValues) {
125
+ slotValues[subIndex] = value;
126
+ this._isChanged.add(slotIndex);
127
+ }
128
+
129
+ // Increment the length
130
+ this._length += 1;
131
+ this._isChangedLength = true;
132
+ }
133
+
134
+ /**
135
+ * @method delete
136
+ * @description Deletes the u128 value at the specified index by setting it to zero. Does not reorder the array.
137
+ * @param {u64} index - The global index of the u128 value to delete.
138
+ */
139
+ public delete(index: u64): void {
140
+ if (index >= this._length) {
141
+ // If the index is out of bounds, revert the transaction
142
+ throw new Revert('Delete operation failed: Index out of bounds.');
143
+ }
144
+
145
+ const slotIndex: u32 = <u32>(index / 2);
146
+ const subIndex: u8 = <u8>(index % 2);
147
+ this.ensureValues(slotIndex);
148
+
149
+ const slotValues = this._values.get(slotIndex);
150
+ if (slotValues) {
151
+ // Set the targeted u128 to zero
152
+ if (!u128.eq(slotValues[subIndex], u128.Zero)) {
153
+ slotValues[subIndex] = u128.Zero;
154
+ this._isChanged.add(slotIndex);
155
+ }
156
+ }
157
+ }
158
+
159
+ /**
160
+ * @method shift
161
+ * @description Removes the first element of the array by setting it to zero, decrementing the length, and incrementing the startIndex.
162
+ * If the startIndex reaches the maximum value of u64, it wraps around to 0.
163
+ */
164
+ public shift(): void {
165
+ if (this._length === 0) {
166
+ throw new Revert('Shift operation failed: Array is empty.');
167
+ }
168
+
169
+ const currentStartIndex: u64 = this._startIndex;
170
+ const slotIndex: u32 = <u32>(currentStartIndex / 2);
171
+ const subIndex: u8 = <u8>(currentStartIndex % 2);
172
+ this.ensureValues(slotIndex);
173
+
174
+ const slotValues = this._values.get(slotIndex);
175
+ if (slotValues) {
176
+ // Set the current start element to zero
177
+ if (!u128.eq(slotValues[subIndex], u128.Zero)) {
178
+ slotValues[subIndex] = u128.Zero;
179
+ this._isChanged.add(slotIndex);
180
+ }
181
+ }
182
+
183
+ // Decrement the length
184
+ this._length -= 1;
185
+ this._isChangedLength = true;
63
186
 
64
- if (!u128.eq(this._values[slotIndex][subIndex], value)) {
65
- this._values[slotIndex][subIndex] = value;
66
- this._isChanged[slotIndex] = true;
187
+ // Increment the startIndex with wrap-around
188
+ if (this._startIndex < this.MAX_LENGTH - 1) {
189
+ this._startIndex += 1;
190
+ } else {
191
+ this._startIndex = 0;
67
192
  }
193
+ this._isChangedStartIndex = true;
68
194
  }
69
195
 
70
196
  /**
71
197
  * @method save
72
- * @description Persists all cached u128 values to their respective storage slots if any have been modified.
198
+ * @description Persists all cached u128 values, the length, and the startIndex to their respective storage slots if any have been modified.
73
199
  */
74
200
  public save(): void {
75
- const max: u32 = this._values.length;
76
-
77
- for (let slotIndex: u32 = 0; slotIndex < max; slotIndex++) {
78
- if (this._isChanged[slotIndex]) {
79
- const packed = this.packValues(slotIndex);
80
- const storagePointer = this.calculateStoragePointer(slotIndex);
81
- Blockchain.setStorageAt(storagePointer, packed);
82
- this._isChanged[slotIndex] = false;
83
- }
201
+ // Save all changed slots
202
+ const values = this._isChanged.values();
203
+ for (let i: u32 = 0; i < <u32>values.length; i++) {
204
+ const slotIndex = values[i];
205
+ const packed = this.packValues(slotIndex);
206
+ const storagePointer = this.calculateStoragePointer(slotIndex);
207
+ Blockchain.setStorageAt(storagePointer, packed);
208
+ }
209
+ this._isChanged.clear();
210
+
211
+ // Save length and startIndex if changed
212
+ if (this._isChangedLength || this._isChangedStartIndex) {
213
+ const packedLengthAndStartIndex = new u256();
214
+ packedLengthAndStartIndex.lo1 = this._length;
215
+ packedLengthAndStartIndex.lo2 = this._startIndex;
216
+ Blockchain.setStorageAt(this.lengthPointer, packedLengthAndStartIndex);
217
+ this._isChangedLength = false;
218
+ this._isChangedStartIndex = false;
84
219
  }
85
220
  }
86
221
 
87
222
  /**
88
- * @method delete
89
- * @description Deletes all storage slots by setting them to zero.
223
+ * @method deleteAll
224
+ * @description Deletes all storage slots by setting them to zero, including the length and startIndex slots.
90
225
  */
91
- public delete(): void {
92
- const max: u32 = this._values.length;
93
- for (let slotIndex: u32 = 0; slotIndex < max; slotIndex++) {
226
+ public deleteAll(): void {
227
+ // Iterate over all loaded slots and clear them
228
+ const keys = this._values.keys();
229
+ for (let i = 0; i < keys.length; i++) {
230
+ const slotIndex = keys[i];
94
231
  const storagePointer = this.calculateStoragePointer(slotIndex);
95
232
  Blockchain.setStorageAt(storagePointer, u256.Zero);
96
233
  }
234
+
235
+ // Reset the length and startIndex to zero
236
+ const zeroLengthAndStartIndex = u256.Zero;
237
+ Blockchain.setStorageAt(this.lengthPointer, zeroLengthAndStartIndex);
238
+ this._length = 0;
239
+ this._startIndex = 0;
240
+ this._isChangedLength = false;
241
+ this._isChangedStartIndex = false;
242
+
97
243
  // Clear internal caches
98
- this._values = [];
99
- this._isLoaded = [];
100
- this._isChanged = [];
244
+ this._values.clear();
245
+ this._isLoaded.clear();
246
+ this._isChanged.clear();
101
247
  }
102
248
 
103
249
  /**
@@ -109,7 +255,7 @@ export class StoredU128Array {
109
255
  @inline
110
256
  public setMultiple(startIndex: u32, values: u128[]): void {
111
257
  for (let i: u32 = 0; i < values.length; i++) {
112
- this.set(startIndex + i, values[i]);
258
+ this.set(<u64>(startIndex + i), values[i]);
113
259
  }
114
260
  }
115
261
 
@@ -122,9 +268,10 @@ export class StoredU128Array {
122
268
  */
123
269
  @inline
124
270
  public getAll(startIndex: u32, count: u32): u128[] {
271
+ assert(startIndex + count <= this._length, 'Requested range exceeds array length');
125
272
  const result: u128[] = new Array<u128>(count);
126
273
  for (let i: u32 = 0; i < count; i++) {
127
- result[i] = this.get(startIndex + i);
274
+ result[i] = this.get(<u64>(startIndex + i));
128
275
  }
129
276
  return result;
130
277
  }
@@ -137,13 +284,11 @@ export class StoredU128Array {
137
284
  @inline
138
285
  public toString(): string {
139
286
  let str = '[';
140
- for (let slotIndex: u32 = 0; slotIndex < this._values.length; slotIndex++) {
141
- for (let subIndex: u8 = 0; subIndex < 2; subIndex++) {
142
- const value = this._values[slotIndex][subIndex];
143
- str += value.toString();
144
- if (!(slotIndex === this._values.length - 1 && subIndex === 1)) {
145
- str += ', ';
146
- }
287
+ for (let i: u32 = 0; i < this._length; i++) {
288
+ const value = this.get(<u64>i);
289
+ str += value.toString();
290
+ if (i !== this._length - 1) {
291
+ str += ', ';
147
292
  }
148
293
  }
149
294
  str += ']';
@@ -158,11 +303,14 @@ export class StoredU128Array {
158
303
  @inline
159
304
  public toBytes(): u8[] {
160
305
  const bytes: u8[] = new Array<u8>();
161
- for (let slotIndex: u32 = 0; slotIndex < this._values.length; slotIndex++) {
162
- const packed = this.packValues(slotIndex);
306
+ const slotCount: u32 = <u32>((this._length + 1) / 2);
307
+
308
+ for (let i: u32 = 0; i < slotCount; i++) {
309
+ this.ensureValues(i);
310
+ const packed = this.packValues(i);
163
311
  const slotBytes = packed.toBytes();
164
- for (let i: u32 = 0; i < slotBytes.length; i++) {
165
- bytes.push(slotBytes[i]);
312
+ for (let j: u32 = 0; j < slotBytes.length; j++) {
313
+ bytes.push(slotBytes[j]);
166
314
  }
167
315
  }
168
316
  return bytes;
@@ -170,40 +318,63 @@ export class StoredU128Array {
170
318
 
171
319
  /**
172
320
  * @method reset
173
- * @description Resets all cached u128 values to zero and marks them as changed.
321
+ * @description Resets all cached u128 values to zero and marks them as changed, including resetting the length and startIndex.
174
322
  */
175
323
  @inline
176
324
  public reset(): void {
177
- for (let slotIndex: u32 = 0; slotIndex < this._values.length; slotIndex++) {
178
- for (let subIndex: u8 = 0; subIndex < 2; subIndex++) {
179
- this._values[slotIndex][subIndex] = u128.Zero;
325
+ // Reset the length and startIndex to zero
326
+ this._length = 0;
327
+ this._startIndex = 0;
328
+ this._isChangedLength = true;
329
+ this._isChangedStartIndex = true;
330
+
331
+ this.save();
332
+ }
333
+
334
+ /**
335
+ * @method getLength
336
+ * @description Retrieves the current length of the array.
337
+ * @returns {u64} - The current length.
338
+ */
339
+ @inline
340
+ public getLength(): u64 {
341
+ return this._length;
342
+ }
343
+
344
+ /**
345
+ * @method setLength
346
+ * @description Sets the length of the array.
347
+ * @param {u64} newLength - The new length to set.
348
+ */
349
+ public setLength(newLength: u64): void {
350
+ if (newLength > this.MAX_LENGTH) {
351
+ throw new Revert('SetLength operation failed: Length exceeds maximum allowed value.');
352
+ }
353
+
354
+ if (newLength < this._length) {
355
+ // Truncate the array if newLength is smaller
356
+ for (let i: u64 = newLength; i < this._length; i++) {
357
+ this.delete(i);
180
358
  }
181
- this._isChanged[slotIndex] = true;
182
359
  }
360
+
361
+ this._length = newLength;
362
+ this._isChangedLength = true;
183
363
  }
184
364
 
185
365
  /**
186
366
  * @private
187
367
  * @method ensureValues
188
- * @description Loads and unpacks the u256 value from the specified storage slot into two u128 cache variables.
368
+ * @description Loads and caches the u128 values from the specified storage slot.
189
369
  * @param {u32} slotIndex - The index of the storage slot.
190
370
  */
191
371
  private ensureValues(slotIndex: u32): void {
192
- // Initialize arrays if necessary
193
- while (slotIndex >= <u32>this._isLoaded.length) {
194
- this._isLoaded.push(false);
195
- this._isChanged.push(false);
196
- const newSlotValues: u128[] = new Array<u128>(2);
197
- newSlotValues[0] = u128.Zero;
198
- newSlotValues[1] = u128.Zero;
199
- this._values.push(newSlotValues);
200
- }
201
-
202
- if (!this._isLoaded[slotIndex]) {
372
+ if (!this._isLoaded.has(slotIndex)) {
203
373
  const storagePointer = this.calculateStoragePointer(slotIndex);
204
374
  const storedU256: u256 = Blockchain.getStorageAt(storagePointer, this.defaultValue);
205
- this._values[slotIndex] = this.unpackU256(storedU256);
206
- this._isLoaded[slotIndex] = true;
375
+ const slotValues = this.unpackU256(storedU256);
376
+ this._values.set(slotIndex, slotValues);
377
+ this._isLoaded.add(slotIndex);
207
378
  }
208
379
  }
209
380
 
@@ -211,19 +382,17 @@ export class StoredU128Array {
211
382
  * @private
212
383
  * @method packValues
213
384
  * @description Packs the two cached u128 values into a single u256 for storage.
214
- * @param {u32} slotIndex - The index of the storage slot.
385
+ * @param {u64} slotIndex - The index of the storage slot.
215
386
  * @returns {u256} - The packed u256 value.
216
387
  */
217
- private packValues(slotIndex: u32): u256 {
218
- const values = this._values[slotIndex];
388
+ private packValues(slotIndex: u64): u256 {
389
+ const values = this._values.get(slotIndex);
390
+ if (!values) {
391
+ // Should not happen, as ensureValues should have loaded it
392
+ return u256.Zero;
393
+ }
219
394
  const packed = new u256();
220
395
 
221
- // Each u256 has lo1, lo2, hi1, hi2 as u64s
222
- // Each u128 consists of two u64s
223
- // Assign values accordingly:
224
- // values[0] -> lo1 (first u64), lo2 (second u64)
225
- // values[1] -> hi1 (third u64), hi2 (fourth u64)
226
-
227
396
  // Assign first u128
228
397
  packed.lo1 = values[0].lo;
229
398
  packed.lo2 = values[0].hi;
@@ -257,15 +426,11 @@ export class StoredU128Array {
257
426
  /**
258
427
  * @private
259
428
  * @method calculateStoragePointer
260
- * @description Calculates the storage pointer for a given slot index by incrementing the subpointer.
261
- * @param {u32} slotIndex - The index of the storage slot.
429
+ * @description Calculates the storage pointer for a given slot index by incrementing the base pointer.
430
+ * @param {u64} slotIndex - The index of the storage slot.
262
431
  * @returns {u256} - The calculated storage pointer.
263
432
  */
264
- private calculateStoragePointer(slotIndex: u32): u256 {
265
- // Clone the base subpointer and increment it by slotIndex
266
- const modifiedSubPointer = this.baseU256Pointer.clone();
267
- SafeMath.add(modifiedSubPointer, u256.fromU32(slotIndex));
268
-
269
- return modifiedSubPointer;
433
+ private calculateStoragePointer(slotIndex: u64): u256 {
434
+ return SafeMath.add(this.baseU256Pointer, u256.fromU64(slotIndex + 1));
270
435
  }
271
436
  }