@btc-vision/btc-runtime 1.10.11 → 1.11.0-alpha

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.
Files changed (41) hide show
  1. package/README.md +48 -224
  2. package/SECURITY.md +38 -191
  3. package/docs/README.md +28 -0
  4. package/docs/advanced/contract-upgrades.md +537 -0
  5. package/docs/advanced/plugins.md +90 -25
  6. package/docs/api-reference/blockchain.md +48 -14
  7. package/docs/api-reference/storage.md +2 -111
  8. package/docs/contracts/op-net-base.md +22 -0
  9. package/docs/contracts/upgradeable.md +396 -0
  10. package/docs/core-concepts/blockchain-environment.md +0 -2
  11. package/docs/core-concepts/security.md +8 -111
  12. package/docs/core-concepts/storage-system.md +1 -32
  13. package/docs/examples/nft-with-reservations.md +8 -238
  14. package/docs/storage/memory-maps.md +1 -44
  15. package/docs/storage/stored-arrays.md +1 -65
  16. package/docs/storage/stored-maps.md +1 -73
  17. package/docs/storage/stored-primitives.md +2 -49
  18. package/docs/types/bytes-writer-reader.md +76 -0
  19. package/docs/types/safe-math.md +2 -45
  20. package/package.json +5 -5
  21. package/runtime/buffer/BytesReader.ts +90 -3
  22. package/runtime/buffer/BytesWriter.ts +81 -3
  23. package/runtime/contracts/OP721.ts +40 -4
  24. package/runtime/contracts/OP_NET.ts +83 -11
  25. package/runtime/contracts/Upgradeable.ts +242 -0
  26. package/runtime/env/BlockchainEnvironment.ts +124 -27
  27. package/runtime/env/global.ts +24 -0
  28. package/runtime/events/upgradeable/UpgradeableEvents.ts +41 -0
  29. package/runtime/generic/AddressMap.ts +20 -18
  30. package/runtime/generic/ExtendedAddressMap.ts +147 -0
  31. package/runtime/generic/MapUint8Array.ts +20 -18
  32. package/runtime/index.ts +8 -0
  33. package/runtime/plugins/Plugin.ts +34 -0
  34. package/runtime/plugins/UpgradeablePlugin.ts +279 -0
  35. package/runtime/storage/BaseStoredString.ts +1 -1
  36. package/runtime/storage/arrays/StoredPackedArray.ts +4 -0
  37. package/runtime/types/ExtendedAddress.ts +36 -24
  38. package/runtime/types/ExtendedAddressCache.ts +27 -0
  39. package/runtime/types/SafeMath.ts +109 -18
  40. package/runtime/types/SchnorrSignature.ts +44 -0
  41. package/runtime/utils/lengths.ts +2 -0
@@ -236,52 +236,9 @@ const approxLn = SafeMath.approxLog(value); // Approximate ln(x) * 1e6
236
236
  const bits = SafeMath.bitLength256(value); // Returns u32
237
237
  ```
238
238
 
239
- ## Operations for Other Integer Sizes
239
+ ## Other Integer Sizes
240
240
 
241
- SafeMath provides variants for u128 and u64 for more efficient operations when u256 is not needed:
242
-
243
- ### u128 Operations
244
-
245
- ```typescript
246
- import { u128 } from '@btc-vision/as-bignum/assembly';
247
-
248
- const a = u128.fromU64(100);
249
- const b = u128.fromU64(50);
250
-
251
- SafeMath.add128(a, b); // Addition
252
- SafeMath.sub128(a, b); // Subtraction
253
- SafeMath.mul128(a, b); // Multiplication
254
- SafeMath.div128(a, b); // Division
255
- SafeMath.min128(a, b); // Minimum
256
- SafeMath.max128(a, b); // Maximum
257
- SafeMath.shl128(a, 10); // Left shift
258
- ```
259
-
260
- ### u64 Operations
261
-
262
- ```typescript
263
- const x: u64 = 100;
264
- const y: u64 = 50;
265
-
266
- SafeMath.add64(x, y); // Addition
267
- SafeMath.sub64(x, y); // Subtraction
268
- SafeMath.mul64(x, y); // Multiplication
269
- SafeMath.div64(x, y); // Division
270
- SafeMath.min64(x, y); // Minimum
271
- SafeMath.max64(x, y); // Maximum
272
- ```
273
-
274
- ## Solidity Comparison
275
-
276
- | Solidity | SafeMath |
277
- |----------|----------|
278
- | `a + b` (checked) | `SafeMath.add(a, b)` |
279
- | `a - b` (checked) | `SafeMath.sub(a, b)` |
280
- | `a * b` (checked) | `SafeMath.mul(a, b)` |
281
- | `a / b` | `SafeMath.div(a, b)` |
282
- | `a % b` | `SafeMath.mod(a, b)` |
283
- | `a ** b` | `SafeMath.pow(a, b)` |
284
- | `mulmod(a, b, n)` | `SafeMath.mulmod(a, b, n)` |
241
+ SafeMath also provides u128 and u64 variants. See [SafeMath API Reference](../api-reference/safe-math.md#u128-operations) for the complete list.
285
242
 
286
243
  ## SafeMathI128
287
244
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btc-vision/btc-runtime",
3
- "version": "1.10.11",
3
+ "version": "1.11.0-alpha",
4
4
  "description": "Bitcoin L1 Smart Contract Runtime for OPNet. Build decentralized applications on Bitcoin using AssemblyScript and WebAssembly. Fully audited.",
5
5
  "main": "btc/index.ts",
6
6
  "types": "btc/index.ts",
@@ -56,17 +56,17 @@
56
56
  "docs"
57
57
  ],
58
58
  "engines": {
59
- "node": ">=22.0.0"
59
+ "node": ">=24.0.0"
60
60
  },
61
61
  "dependencies": {
62
62
  "@assemblyscript/loader": "^0.28.9",
63
63
  "@btc-vision/as-bignum": "^0.0.7",
64
64
  "@btc-vision/opnet-transform": "^0.2.1",
65
- "@eslint/js": "9.39.1",
65
+ "@eslint/js": "9.39.2",
66
66
  "gulplog": "^2.2.0",
67
67
  "ts-node": "^10.9.2",
68
68
  "typescript": "^5.9.3",
69
- "typescript-eslint": "^8.46.3"
69
+ "typescript-eslint": "^8.53.1"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@btc-vision/as-covers-assembly": "^0.4.4",
@@ -74,7 +74,7 @@
74
74
  "@btc-vision/as-pect-assembly": "^8.2.0",
75
75
  "@btc-vision/as-pect-cli": "^8.2.0",
76
76
  "@btc-vision/as-pect-transform": "^8.2.0",
77
- "@types/node": "^24.10.1",
77
+ "@types/node": "^25.0.9",
78
78
  "assemblyscript": "^0.28.9"
79
79
  }
80
80
  }
@@ -1,15 +1,18 @@
1
1
  import { i128, u128, u256 } from '@btc-vision/as-bignum/assembly';
2
2
  import { AddressMap } from '../generic/AddressMap';
3
+ import { ExtendedAddressMap } from '../generic/ExtendedAddressMap';
3
4
  import { Selector } from '../math/abi';
4
5
  import { Address } from '../types/Address';
5
6
  import { Revert } from '../types/Revert';
6
7
  import {
7
8
  ADDRESS_BYTE_LENGTH,
9
+ EXTENDED_ADDRESS_BYTE_LENGTH,
8
10
  I128_BYTE_LENGTH,
9
11
  I16_BYTE_LENGTH,
10
12
  I32_BYTE_LENGTH,
11
13
  I64_BYTE_LENGTH,
12
14
  I8_BYTE_LENGTH,
15
+ SCHNORR_SIGNATURE_BYTE_LENGTH,
13
16
  U128_BYTE_LENGTH,
14
17
  U16_BYTE_LENGTH,
15
18
  U256_BYTE_LENGTH,
@@ -17,6 +20,8 @@ import {
17
20
  U64_BYTE_LENGTH,
18
21
  U8_BYTE_LENGTH,
19
22
  } from '../utils';
23
+ import { ExtendedAddress } from '../types/ExtendedAddress';
24
+ import { SchnorrSignature } from '../types/SchnorrSignature';
20
25
 
21
26
  @final
22
27
  export class BytesReader {
@@ -32,8 +37,7 @@ export class BytesReader {
32
37
  }
33
38
 
34
39
  public read<T>(): T {
35
- const id = idof<T>();
36
-
40
+ // Handle primitives first (before calling idof which doesn't work on primitives)
37
41
  if (isBoolean<T>()) {
38
42
  return this.readBoolean() as T;
39
43
  } else if (isString<T>()) {
@@ -70,12 +74,19 @@ export class BytesReader {
70
74
  throw new Revert(`Invalid size ${size}`);
71
75
  }
72
76
  }
73
- } else if (id === idof<u256>()) {
77
+ }
78
+
79
+ // For reference types, use idof
80
+ const id = idof<T>();
81
+
82
+ if (id === idof<u256>()) {
74
83
  return this.readU256() as T;
75
84
  } else if (id === idof<u128>()) {
76
85
  return this.readU128() as T;
77
86
  } else if (id === idof<i128>()) {
78
87
  return this.readI128() as T;
88
+ } else if (id === idof<ExtendedAddress>()) {
89
+ return this.readExtendedAddress() as T;
79
90
  } else if (id === idof<Address>()) {
80
91
  return this.readAddress() as T;
81
92
  } else if (id === idof<Uint8Array>()) {
@@ -245,6 +256,48 @@ export class BytesReader {
245
256
  return addr;
246
257
  }
247
258
 
259
+ /**
260
+ * Reads an ExtendedAddress (64 bytes).
261
+ * Format: [32 bytes tweakedPublicKey][32 bytes ML-DSA key hash]
262
+ *
263
+ * @returns The ExtendedAddress read from the buffer
264
+ */
265
+ public readExtendedAddress(): ExtendedAddress {
266
+ this.verifyEnd(this.currentOffset + EXTENDED_ADDRESS_BYTE_LENGTH);
267
+
268
+ // Read tweaked public key (32 bytes)
269
+ const tweakedPublicKey: u8[] = new Array<u8>(ADDRESS_BYTE_LENGTH);
270
+ for (let i: i32 = 0; i < ADDRESS_BYTE_LENGTH; i++) {
271
+ tweakedPublicKey[i] = this.readU8();
272
+ }
273
+
274
+ // Read ML-DSA key hash (32 bytes)
275
+ const publicKey: u8[] = new Array<u8>(ADDRESS_BYTE_LENGTH);
276
+ for (let i: i32 = 0; i < ADDRESS_BYTE_LENGTH; i++) {
277
+ publicKey[i] = this.readU8();
278
+ }
279
+
280
+ return new ExtendedAddress(tweakedPublicKey, publicKey);
281
+ }
282
+
283
+ /**
284
+ * Reads a Schnorr signature with its associated ExtendedAddress.
285
+ * Format: [64 bytes ExtendedAddress][64 bytes signature]
286
+ *
287
+ * Used for deserializing signed data where both the signer's address
288
+ * and their Schnorr signature are stored together.
289
+ *
290
+ * @returns A SchnorrSignature containing the address and signature
291
+ */
292
+ public readSchnorrSignature(): SchnorrSignature {
293
+ this.verifyEnd(this.currentOffset + EXTENDED_ADDRESS_BYTE_LENGTH + SCHNORR_SIGNATURE_BYTE_LENGTH);
294
+
295
+ const address = this.readExtendedAddress();
296
+ const signature = this.readBytes(SCHNORR_SIGNATURE_BYTE_LENGTH);
297
+
298
+ return new SchnorrSignature(address, signature);
299
+ }
300
+
248
301
  // ------------------- Arrays ------------------- //
249
302
 
250
303
  public readArrayOfBuffer(be: boolean = true): Uint8Array[] {
@@ -336,6 +389,40 @@ export class BytesReader {
336
389
  return result;
337
390
  }
338
391
 
392
+ /**
393
+ * Reads an array of ExtendedAddress (64 bytes each).
394
+ * Format: [u16 length][ExtendedAddress 0][ExtendedAddress 1]...
395
+ */
396
+ public readExtendedAddressArray(be: boolean = true): ExtendedAddress[] {
397
+ const length = this.readU16(be);
398
+ const result = new Array<ExtendedAddress>(length);
399
+ for (let i: u16 = 0; i < length; i++) {
400
+ result[i] = this.readExtendedAddress();
401
+ }
402
+ return result;
403
+ }
404
+
405
+ /**
406
+ * Reads a map of ExtendedAddress -> u256.
407
+ * Format: [u16 length][ExtendedAddress key][u256 value]...
408
+ */
409
+ public readExtendedAddressMapU256(be: boolean = true): ExtendedAddressMap<u256> {
410
+ const length = this.readU16(be);
411
+ const result = new ExtendedAddressMap<u256>();
412
+
413
+ for (let i: u16 = 0; i < length; i++) {
414
+ const address = this.readExtendedAddress();
415
+ const value = this.readU256(be);
416
+
417
+ if (result.has(address)) {
418
+ throw new Revert('Duplicate extended address found in map');
419
+ }
420
+ result.set(address, value);
421
+ }
422
+
423
+ return result;
424
+ }
425
+
339
426
  public getOffset(): i32 {
340
427
  return this.currentOffset;
341
428
  }
@@ -1,15 +1,18 @@
1
1
  import { i128, u128, u256 } from '@btc-vision/as-bignum/assembly';
2
2
  import { AddressMap } from '../generic/AddressMap';
3
+ import { ExtendedAddressMap } from '../generic/ExtendedAddressMap';
3
4
  import { Selector } from '../math/abi';
4
5
  import { Address } from '../types/Address';
5
6
  import { Revert } from '../types/Revert';
6
7
  import {
7
8
  ADDRESS_BYTE_LENGTH,
9
+ EXTENDED_ADDRESS_BYTE_LENGTH,
8
10
  I128_BYTE_LENGTH,
9
11
  I16_BYTE_LENGTH,
10
12
  I32_BYTE_LENGTH,
11
13
  I64_BYTE_LENGTH,
12
14
  I8_BYTE_LENGTH,
15
+ SCHNORR_SIGNATURE_BYTE_LENGTH,
13
16
  U128_BYTE_LENGTH,
14
17
  U16_BYTE_LENGTH,
15
18
  U256_BYTE_LENGTH,
@@ -17,6 +20,7 @@ import {
17
20
  U64_BYTE_LENGTH,
18
21
  U8_BYTE_LENGTH,
19
22
  } from '../utils';
23
+ import { ExtendedAddress } from '../types/ExtendedAddress';
20
24
  import { BytesReader } from './BytesReader';
21
25
 
22
26
  const arrayTooLargeError: string = 'Array is too large';
@@ -90,10 +94,14 @@ export class BytesWriter {
90
94
  this.writeBoolean(<boolean>value);
91
95
  } else if (isString<T>()) {
92
96
  this.writeStringWithLength(<string>value);
93
- } else if (value instanceof Uint8Array) {
94
- this.writeBytesWithLength(<Uint8Array>value);
97
+ } else if (value instanceof ExtendedAddress) {
98
+ // Check ExtendedAddress before Address (ExtendedAddress extends Address)
99
+ this.writeExtendedAddress(<ExtendedAddress>value);
95
100
  } else if (value instanceof Address) {
101
+ // Check Address before Uint8Array (Address extends Uint8Array)
96
102
  this.writeAddress(<Address>value);
103
+ } else if (value instanceof Uint8Array) {
104
+ this.writeBytesWithLength(<Uint8Array>value);
97
105
  } else if (value instanceof u128) {
98
106
  this.writeU128(<u128>value);
99
107
  } else if (value instanceof u256) {
@@ -291,6 +299,20 @@ export class BytesWriter {
291
299
  }
292
300
  }
293
301
 
302
+ /**
303
+ * Writes an array of ExtendedAddress (64 bytes each).
304
+ * Format: [u16 length][ExtendedAddress 0][ExtendedAddress 1]...
305
+ */
306
+ public writeExtendedAddressArray(value: ExtendedAddress[]): void {
307
+ if (value.length > 65535) throw new Revert(arrayTooLargeError);
308
+ this.allocSafe(U16_BYTE_LENGTH + value.length * EXTENDED_ADDRESS_BYTE_LENGTH);
309
+ this.writeU16(u16(value.length));
310
+
311
+ for (let i: i32 = 0; i < value.length; i++) {
312
+ this.writeExtendedAddress(value[i]);
313
+ }
314
+ }
315
+
294
316
  // --------------------------------------------------- //
295
317
 
296
318
  public writeBytes(value: Uint8Array): void {
@@ -336,6 +358,42 @@ export class BytesWriter {
336
358
  this.writeBytes(bytes);
337
359
  }
338
360
 
361
+ /**
362
+ * Writes an ExtendedAddress (64 bytes total).
363
+ * Format: [32 bytes tweakedPublicKey][32 bytes ML-DSA key hash]
364
+ *
365
+ * @param value - The ExtendedAddress to write
366
+ */
367
+ public writeExtendedAddress(value: ExtendedAddress): void {
368
+ this.allocSafe(EXTENDED_ADDRESS_BYTE_LENGTH);
369
+ // Write tweaked public key first (32 bytes)
370
+ this.writeBytes(value.tweakedPublicKey);
371
+ // Write ML-DSA key hash (32 bytes) - inherited from Address
372
+ this.writeBytes(value);
373
+ }
374
+
375
+ /**
376
+ * Writes a Schnorr signature with its associated ExtendedAddress.
377
+ * Format: [64 bytes ExtendedAddress][64 bytes signature]
378
+ *
379
+ * Used for serializing signed data where both the signer's address
380
+ * and their Schnorr signature need to be stored together.
381
+ *
382
+ * @param address - The signer's ExtendedAddress (64 bytes)
383
+ * @param signature - The 64-byte Schnorr signature
384
+ * @throws {Revert} If signature is not exactly 64 bytes
385
+ */
386
+ public writeSchnorrSignature(address: ExtendedAddress, signature: Uint8Array): void {
387
+ if (signature.length !== SCHNORR_SIGNATURE_BYTE_LENGTH) {
388
+ throw new Revert(
389
+ `Invalid Schnorr signature length: expected ${SCHNORR_SIGNATURE_BYTE_LENGTH}, got ${signature.length}`,
390
+ );
391
+ }
392
+ this.allocSafe(EXTENDED_ADDRESS_BYTE_LENGTH + SCHNORR_SIGNATURE_BYTE_LENGTH);
393
+ this.writeExtendedAddress(address);
394
+ this.writeBytes(signature);
395
+ }
396
+
339
397
  // zero-copy bulk writer
340
398
  public writeRaw(data: Uint8Array): void {
341
399
  const n = data.length;
@@ -369,7 +427,7 @@ export class BytesWriter {
369
427
  }
370
428
 
371
429
  /**
372
- * Equivalent to TSs writeAddressValueTuple, except specialized for u256 values.
430
+ * Equivalent to TS's writeAddressValueTuple, except specialized for u256 values.
373
431
  */
374
432
  public writeAddressMapU256(value: AddressMap<u256>, be: boolean = true): void {
375
433
  const keys: Address[] = value.keys();
@@ -383,6 +441,22 @@ export class BytesWriter {
383
441
  }
384
442
  }
385
443
 
444
+ /**
445
+ * Writes a map of ExtendedAddress -> u256.
446
+ * Format: [u16 length][ExtendedAddress key][u256 value]...
447
+ */
448
+ public writeExtendedAddressMapU256(value: ExtendedAddressMap<u256>, be: boolean = true): void {
449
+ const keys: ExtendedAddress[] = value.keys();
450
+ if (keys.length > 65535) throw new Revert('Map size is too large');
451
+
452
+ this.writeU16(u16(keys.length), be);
453
+
454
+ for (let i: i32 = 0; i < keys.length; i++) {
455
+ this.writeExtendedAddress(keys[i]);
456
+ this.writeU256(value.get(keys[i]), be);
457
+ }
458
+ }
459
+
386
460
  // --------------------------------------------------- //
387
461
 
388
462
  public getBuffer(): Uint8Array {
@@ -402,6 +476,10 @@ export class BytesWriter {
402
476
  * If not, calls `resize()` which by default throws a Revert.
403
477
  */
404
478
  public allocSafe(size: u32): void {
479
+ if (size > u32.MAX_VALUE - this.currentOffset) {
480
+ throw new Revert('BytesWriter: offset overflow');
481
+ }
482
+
405
483
  const needed = this.currentOffset + size;
406
484
  if (needed > u32(this.buffer.byteLength)) {
407
485
  const sizeDiff: u32 = needed - u32(this.buffer.byteLength);
@@ -1,3 +1,6 @@
1
+ // THIS STANDARD IS EXPERIMENTAL AND SHOULDN'T BE USED IN REAL PROJECTS
2
+ // CONTRACTS USING THIS COULD BREAK IN THE FUTURE
3
+
1
4
  import { u256 } from '@btc-vision/as-bignum/assembly';
2
5
  import { BytesWriter } from '../buffer/BytesWriter';
3
6
  import { Blockchain } from '../env';
@@ -305,6 +308,36 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
305
308
  return w;
306
309
  }
307
310
 
311
+ @method(
312
+ { name: 'to', type: ABIDataTypes.ADDRESS },
313
+ { name: 'tokenId', type: ABIDataTypes.UINT256 },
314
+ )
315
+ @emit('Transferred')
316
+ public transfer(calldata: Calldata): BytesWriter {
317
+ const to = calldata.readAddress();
318
+ const tokenId = calldata.readU256();
319
+
320
+ this._transfer(Blockchain.tx.sender, to, tokenId);
321
+
322
+ return new BytesWriter(0);
323
+ }
324
+
325
+ @method(
326
+ { name: 'from', type: ABIDataTypes.ADDRESS },
327
+ { name: 'to', type: ABIDataTypes.ADDRESS },
328
+ { name: 'tokenId', type: ABIDataTypes.UINT256 },
329
+ )
330
+ @emit('Transferred')
331
+ public transferFrom(calldata: Calldata): BytesWriter {
332
+ const from = calldata.readAddress();
333
+ const to = calldata.readAddress();
334
+ const amount = calldata.readU256();
335
+
336
+ this._transfer(from, to, amount);
337
+
338
+ return new BytesWriter(0);
339
+ }
340
+
308
341
  @method(
309
342
  { name: 'to', type: ABIDataTypes.ADDRESS },
310
343
  { name: 'tokenId', type: ABIDataTypes.UINT256 },
@@ -316,7 +349,7 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
316
349
  const tokenId = calldata.readU256();
317
350
  const data = calldata.readBytesWithLength();
318
351
 
319
- this._transfer(Blockchain.tx.sender, to, tokenId, data);
352
+ this._safeTransfer(Blockchain.tx.sender, to, tokenId, data);
320
353
 
321
354
  return new BytesWriter(0);
322
355
  }
@@ -334,7 +367,7 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
334
367
  const tokenId = calldata.readU256();
335
368
  const data = calldata.readBytesWithLength();
336
369
 
337
- this._transfer(from, to, tokenId, data);
370
+ this._safeTransfer(from, to, tokenId, data);
338
371
 
339
372
  return new BytesWriter(0);
340
373
  }
@@ -637,7 +670,7 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
637
670
  this.createTransferEvent(owner, Address.zero(), tokenId);
638
671
  }
639
672
 
640
- protected _transfer(from: Address, to: Address, tokenId: u256, data: Uint8Array): void {
673
+ protected _transfer(from: Address, to: Address, tokenId: u256): void {
641
674
  // Skip self-transfers
642
675
  if (from === to) return;
643
676
 
@@ -680,8 +713,11 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
680
713
  this.ownerOfMap.set(tokenId, this._u256FromAddress(to));
681
714
 
682
715
  this.createTransferEvent(from, to, tokenId);
716
+ }
717
+
718
+ protected _safeTransfer(from: Address, to: Address, tokenId: u256, data: Uint8Array): void {
719
+ this._transfer(from, to, tokenId);
683
720
 
684
- // External call happens after all state changes
685
721
  if (Blockchain.isContract(to)) {
686
722
  this._checkOnOP721Received(from, to, tokenId, data);
687
723
  }
@@ -7,8 +7,15 @@ import { Calldata } from '../types';
7
7
  import { Address } from '../types/Address';
8
8
  import { Revert } from '../types/Revert';
9
9
  import { ADDRESS_BYTE_LENGTH } from '../utils';
10
+ import { Plugin } from '../plugins/Plugin';
10
11
 
11
12
  export class OP_NET implements IBTC {
13
+ /**
14
+ * Registered plugins that can handle method selectors.
15
+ * Plugins are checked in registration order when execute() is called.
16
+ */
17
+ private readonly _plugins: Plugin[] = [];
18
+
12
19
  public get address(): Address {
13
20
  return Blockchain.contractAddress;
14
21
  }
@@ -17,25 +24,90 @@ export class OP_NET implements IBTC {
17
24
  return Blockchain.contractDeployer;
18
25
  }
19
26
 
20
- public onDeployment(_calldata: Calldata): void {}
27
+ public onDeployment(calldata: Calldata): void {
28
+ // Call onDeployment for all registered plugins
29
+ for (let i = 0; i < this._plugins.length; i++) {
30
+ this._plugins[i].onDeployment(calldata);
31
+ }
32
+ }
21
33
 
22
- public onExecutionStarted(_selector: Selector, _calldata: Calldata): void {}
34
+ /**
35
+ * Called when the contract's bytecode is updated via updateContractFromExisting.
36
+ * Override this method to perform migration logic when the contract is upgraded.
37
+ *
38
+ * @param calldata - The calldata passed to updateContractFromExisting
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * public override onUpdate(calldata: Calldata): void {
43
+ * super.onUpdate(calldata); // Call plugins
44
+ * const version = calldata.readU64();
45
+ * // Perform migration based on version
46
+ * }
47
+ * ```
48
+ */
49
+ public onUpdate(calldata: Calldata): void {
50
+ // Call onUpdate for all registered plugins
51
+ for (let i = 0; i < this._plugins.length; i++) {
52
+ this._plugins[i].onUpdate(calldata);
53
+ }
54
+ }
23
55
 
24
- public onExecutionCompleted(_selector: Selector, _calldata: Calldata): void {}
56
+ public onExecutionStarted(selector: Selector, calldata: Calldata): void {
57
+ // Call onExecutionStarted for all registered plugins
58
+ for (let i = 0; i < this._plugins.length; i++) {
59
+ this._plugins[i].onExecutionStarted(selector, calldata);
60
+ }
61
+ }
25
62
 
26
- public execute(method: Selector, _calldata: Calldata): BytesWriter {
27
- let response: BytesWriter;
63
+ public onExecutionCompleted(selector: Selector, calldata: Calldata): void {
64
+ // Call onExecutionCompleted for all registered plugins
65
+ for (let i = 0; i < this._plugins.length; i++) {
66
+ this._plugins[i].onExecutionCompleted(selector, calldata);
67
+ }
68
+ }
28
69
 
70
+ public execute(method: Selector, calldata: Calldata): BytesWriter {
71
+ // Check built-in methods first
29
72
  switch (method) {
30
- case encodeSelector('deployer()'):
31
- response = new BytesWriter(ADDRESS_BYTE_LENGTH);
73
+ case encodeSelector('deployer()'): {
74
+ const response = new BytesWriter(ADDRESS_BYTE_LENGTH);
32
75
  response.writeAddress(this.contractDeployer);
33
- break;
34
- default:
35
- throw new Revert(`Method not found: ${method}`);
76
+ return response;
77
+ }
36
78
  }
37
79
 
38
- return response;
80
+ // Try registered plugins
81
+ const startOffset = calldata.getOffset();
82
+ for (let i = 0; i < this._plugins.length; i++) {
83
+ const result = this._plugins[i].execute(method, calldata);
84
+ if (result !== null) {
85
+ return result;
86
+ }
87
+
88
+ calldata.setOffset(startOffset);
89
+ }
90
+
91
+ // No handler found
92
+ throw new Revert(`Method not found: ${method}`);
93
+ }
94
+
95
+ /**
96
+ * Registers a plugin to handle method selectors automatically.
97
+ * Plugins are checked in registration order when the contract's execute() falls through to super.
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * public constructor() {
102
+ * super();
103
+ * this.registerPlugin(new UpgradeablePlugin(144));
104
+ * }
105
+ * ```
106
+ *
107
+ * @param plugin - The plugin to register
108
+ */
109
+ protected registerPlugin(plugin: Plugin): void {
110
+ this._plugins.push(plugin);
39
111
  }
40
112
 
41
113
  protected emitEvent(event: NetEvent): void {