@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.
- package/README.md +48 -224
- package/SECURITY.md +38 -191
- package/docs/README.md +28 -0
- package/docs/advanced/contract-upgrades.md +537 -0
- package/docs/advanced/plugins.md +90 -25
- package/docs/api-reference/blockchain.md +48 -14
- package/docs/api-reference/storage.md +2 -111
- package/docs/contracts/op-net-base.md +22 -0
- package/docs/contracts/upgradeable.md +396 -0
- package/docs/core-concepts/blockchain-environment.md +0 -2
- package/docs/core-concepts/security.md +8 -111
- package/docs/core-concepts/storage-system.md +1 -32
- package/docs/examples/nft-with-reservations.md +8 -238
- package/docs/storage/memory-maps.md +1 -44
- package/docs/storage/stored-arrays.md +1 -65
- package/docs/storage/stored-maps.md +1 -73
- package/docs/storage/stored-primitives.md +2 -49
- package/docs/types/bytes-writer-reader.md +76 -0
- package/docs/types/safe-math.md +2 -45
- package/package.json +5 -5
- package/runtime/buffer/BytesReader.ts +90 -3
- package/runtime/buffer/BytesWriter.ts +81 -3
- package/runtime/contracts/OP721.ts +40 -4
- package/runtime/contracts/OP_NET.ts +83 -11
- package/runtime/contracts/Upgradeable.ts +242 -0
- package/runtime/env/BlockchainEnvironment.ts +124 -27
- package/runtime/env/global.ts +24 -0
- package/runtime/events/upgradeable/UpgradeableEvents.ts +41 -0
- package/runtime/generic/AddressMap.ts +20 -18
- package/runtime/generic/ExtendedAddressMap.ts +147 -0
- package/runtime/generic/MapUint8Array.ts +20 -18
- package/runtime/index.ts +8 -0
- package/runtime/plugins/Plugin.ts +34 -0
- package/runtime/plugins/UpgradeablePlugin.ts +279 -0
- package/runtime/storage/BaseStoredString.ts +1 -1
- package/runtime/storage/arrays/StoredPackedArray.ts +4 -0
- package/runtime/types/ExtendedAddress.ts +36 -24
- package/runtime/types/ExtendedAddressCache.ts +27 -0
- package/runtime/types/SafeMath.ts +109 -18
- package/runtime/types/SchnorrSignature.ts +44 -0
- package/runtime/utils/lengths.ts +2 -0
package/docs/types/safe-math.md
CHANGED
|
@@ -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
|
-
##
|
|
239
|
+
## Other Integer Sizes
|
|
240
240
|
|
|
241
|
-
SafeMath provides
|
|
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.
|
|
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": ">=
|
|
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.
|
|
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.
|
|
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": "^
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
94
|
-
|
|
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 TS
|
|
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.
|
|
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.
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
27
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
throw new Revert(`Method not found: ${method}`);
|
|
76
|
+
return response;
|
|
77
|
+
}
|
|
36
78
|
}
|
|
37
79
|
|
|
38
|
-
|
|
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 {
|