@btc-vision/btc-runtime 1.9.1 → 1.9.2
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 +13 -4
- package/runtime/constants/Exports.ts +86 -0
- package/runtime/contracts/OP1155.ts +1042 -0
- package/runtime/contracts/OP20.ts +56 -37
- package/runtime/contracts/OP721.ts +882 -0
- package/runtime/contracts/OP_NET.ts +5 -0
- package/runtime/contracts/ReentrancyGuard.ts +136 -0
- package/runtime/contracts/interfaces/IOP1155.ts +33 -0
- package/runtime/contracts/interfaces/IOP721.ts +29 -0
- package/runtime/contracts/interfaces/OP1155InitParameters.ts +11 -0
- package/runtime/contracts/interfaces/OP721InitParameters.ts +15 -0
- package/runtime/env/BlockchainEnvironment.ts +32 -3
- package/runtime/events/predefined/ApprovedForAll.ts +16 -0
- package/runtime/events/predefined/TransferredBatchEvent.ts +35 -0
- package/runtime/events/predefined/TransferredSingleEvent.ts +18 -0
- package/runtime/events/predefined/URIEvent.ts +23 -0
- package/runtime/events/predefined/index.ts +4 -0
- package/runtime/index.ts +9 -0
- package/runtime/math/abi.ts +23 -0
- package/runtime/math/bytes.ts +4 -0
- package/runtime/memory/AddressMemoryMap.ts +20 -0
- package/runtime/nested/storage/StorageMap.ts +3 -23
- package/runtime/nested/storage/StorageSet.ts +6 -3
- package/runtime/script/Script.ts +1 -1
- package/runtime/secp256k1/ECPoint.ts +3 -3
- package/runtime/shared-libraries/OP20Utils.ts +1 -2
- package/runtime/storage/AdvancedStoredString.ts +8 -189
- package/runtime/storage/BaseStoredString.ts +206 -0
- package/runtime/storage/StoredString.ts +15 -194
- package/runtime/storage/arrays/StoredPackedArray.ts +13 -4
- package/runtime/types/SafeMath.ts +125 -94
|
@@ -50,6 +50,11 @@ export class OP_NET implements IBTC {
|
|
|
50
50
|
return this.address === address;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
protected _buildDomainSeparator(): Uint8Array {
|
|
54
|
+
// This method should be overridden in derived classes to provide the domain separator
|
|
55
|
+
throw new Error('Method not implemented.');
|
|
56
|
+
}
|
|
57
|
+
|
|
53
58
|
protected onlyDeployer(caller: Address): void {
|
|
54
59
|
if (this.contractDeployer !== caller) {
|
|
55
60
|
throw new Revert('Only deployer can call this method');
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Blockchain } from '../env';
|
|
2
|
+
import { OP_NET } from './OP_NET';
|
|
3
|
+
import { StoredBoolean } from '../storage/StoredBoolean';
|
|
4
|
+
import { StoredU256 } from '../storage/StoredU256';
|
|
5
|
+
import { Selector } from '../math/abi';
|
|
6
|
+
import { Calldata } from '../types';
|
|
7
|
+
import { Revert } from '../types/Revert';
|
|
8
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
9
|
+
import { SafeMath } from '../types/SafeMath';
|
|
10
|
+
import {
|
|
11
|
+
ON_OP1155_BATCH_RECEIVED_MAGIC,
|
|
12
|
+
ON_OP1155_RECEIVED_MAGIC,
|
|
13
|
+
ON_OP20_RECEIVED_SELECTOR,
|
|
14
|
+
ON_OP721_RECEIVED_SELECTOR,
|
|
15
|
+
} from '../constants/Exports';
|
|
16
|
+
import { EMPTY_POINTER } from '../math/bytes';
|
|
17
|
+
|
|
18
|
+
const statusPointer: u16 = Blockchain.nextPointer;
|
|
19
|
+
const depthPointer: u16 = Blockchain.nextPointer;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @enum ReentrancyLevel
|
|
23
|
+
* @description
|
|
24
|
+
* Defines the level of reentrancy protection.
|
|
25
|
+
*
|
|
26
|
+
* STANDARD: Strict single entry, no reentrancy allowed.
|
|
27
|
+
* CALLBACK: Allows one level of reentrancy for callbacks (e.g., token transfers).
|
|
28
|
+
*/
|
|
29
|
+
export enum ReentrancyLevel {
|
|
30
|
+
STANDARD = 0,
|
|
31
|
+
CALLBACK = 1,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class ReentrancyGuard extends OP_NET {
|
|
35
|
+
protected readonly _locked: StoredBoolean;
|
|
36
|
+
protected readonly _reentrancyDepth: StoredU256;
|
|
37
|
+
|
|
38
|
+
// Override this in derived contracts to set protection level
|
|
39
|
+
protected readonly reentrancyLevel: ReentrancyLevel = ReentrancyLevel.STANDARD;
|
|
40
|
+
|
|
41
|
+
protected constructor() {
|
|
42
|
+
super();
|
|
43
|
+
this._locked = new StoredBoolean(statusPointer, false);
|
|
44
|
+
this._reentrancyDepth = new StoredU256(depthPointer, EMPTY_POINTER);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public override onExecutionCompleted(selector: Selector, calldata: Calldata): void {
|
|
48
|
+
super.onExecutionCompleted(selector, calldata);
|
|
49
|
+
|
|
50
|
+
if (this.isSelectorExcluded(selector)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.nonReentrantAfter();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public override onExecutionStarted(selector: Selector, calldata: Calldata): void {
|
|
58
|
+
super.onExecutionStarted(selector, calldata);
|
|
59
|
+
|
|
60
|
+
if (this.isSelectorExcluded(selector)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.nonReentrantBefore();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public nonReentrantBefore(): void {
|
|
68
|
+
if (this.reentrancyLevel === ReentrancyLevel.STANDARD) {
|
|
69
|
+
// Standard behavior - strict single entry
|
|
70
|
+
if (this._locked.value) {
|
|
71
|
+
this.reentrancyGuardReentrantCall();
|
|
72
|
+
}
|
|
73
|
+
this._locked.value = true;
|
|
74
|
+
} else if (this.reentrancyLevel === ReentrancyLevel.CALLBACK) {
|
|
75
|
+
// Allow one level of reentrancy for callbacks
|
|
76
|
+
const currentDepth = this._reentrancyDepth.value;
|
|
77
|
+
|
|
78
|
+
// Maximum depth of 2 (original call + one callback reentry)
|
|
79
|
+
if (currentDepth >= u256.One) {
|
|
80
|
+
throw new Revert('ReentrancyGuard: Max depth exceeded');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this._reentrancyDepth.value = SafeMath.add(currentDepth, u256.One);
|
|
84
|
+
|
|
85
|
+
// Use locked flag for first entry
|
|
86
|
+
if (currentDepth.isZero()) {
|
|
87
|
+
this._locked.value = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public nonReentrantAfter(): void {
|
|
93
|
+
if (this.reentrancyLevel === ReentrancyLevel.STANDARD) {
|
|
94
|
+
// Standard behavior
|
|
95
|
+
this._locked.value = false;
|
|
96
|
+
} else if (this.reentrancyLevel === ReentrancyLevel.CALLBACK) {
|
|
97
|
+
// Decrement depth
|
|
98
|
+
const currentDepth = this._reentrancyDepth.value;
|
|
99
|
+
if (currentDepth.isZero()) {
|
|
100
|
+
throw new Revert('ReentrancyGuard: Depth underflow');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const newDepth = SafeMath.sub(currentDepth, u256.One);
|
|
104
|
+
this._reentrancyDepth.value = newDepth;
|
|
105
|
+
|
|
106
|
+
// Clear locked flag when fully exited
|
|
107
|
+
if (newDepth.isZero()) {
|
|
108
|
+
this._locked.value = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public reentrancyGuardEntered(): boolean {
|
|
114
|
+
return this._locked.value === true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public getCurrentDepth(): u256 {
|
|
118
|
+
return this._reentrancyDepth.value;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @dev Unauthorized reentrant call.
|
|
123
|
+
*/
|
|
124
|
+
protected reentrancyGuardReentrantCall(): void {
|
|
125
|
+
throw new Revert('ReentrancyGuard: LOCKED');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected isSelectorExcluded(selector: Selector): boolean {
|
|
129
|
+
return (
|
|
130
|
+
selector === ON_OP20_RECEIVED_SELECTOR ||
|
|
131
|
+
selector === ON_OP721_RECEIVED_SELECTOR ||
|
|
132
|
+
selector === ON_OP1155_RECEIVED_MAGIC ||
|
|
133
|
+
selector === ON_OP1155_BATCH_RECEIVED_MAGIC
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { BytesWriter } from '../../buffer/BytesWriter';
|
|
2
|
+
import { Calldata } from '../../types';
|
|
3
|
+
|
|
4
|
+
export interface IOP1155 {
|
|
5
|
+
// Core properties
|
|
6
|
+
fn_name(calldata: Calldata): BytesWriter;
|
|
7
|
+
fn_symbol(calldata: Calldata): BytesWriter;
|
|
8
|
+
uri(calldata: Calldata): BytesWriter;
|
|
9
|
+
|
|
10
|
+
// Balance and supply
|
|
11
|
+
balanceOf(calldata: Calldata): BytesWriter;
|
|
12
|
+
balanceOfBatch(calldata: Calldata): BytesWriter;
|
|
13
|
+
totalSupply(calldata: Calldata): BytesWriter;
|
|
14
|
+
exists(calldata: Calldata): BytesWriter;
|
|
15
|
+
|
|
16
|
+
// Transfer functions
|
|
17
|
+
safeTransferFrom(calldata: Calldata): BytesWriter;
|
|
18
|
+
safeBatchTransferFrom(calldata: Calldata): BytesWriter;
|
|
19
|
+
|
|
20
|
+
// Approval functions
|
|
21
|
+
setApprovalForAll(calldata: Calldata): BytesWriter;
|
|
22
|
+
isApprovedForAll(calldata: Calldata): BytesWriter;
|
|
23
|
+
|
|
24
|
+
// Advanced functions
|
|
25
|
+
burn(calldata: Calldata): BytesWriter;
|
|
26
|
+
burnBatch(calldata: Calldata): BytesWriter;
|
|
27
|
+
transferBySignature(calldata: Calldata): BytesWriter;
|
|
28
|
+
batchTransferBySignature(calldata: Calldata): BytesWriter;
|
|
29
|
+
domainSeparator(calldata: Calldata): BytesWriter;
|
|
30
|
+
|
|
31
|
+
// Interface support
|
|
32
|
+
supportsInterface(calldata: Calldata): BytesWriter;
|
|
33
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BytesWriter } from '../../buffer/BytesWriter';
|
|
2
|
+
import { Calldata } from '../../types';
|
|
3
|
+
|
|
4
|
+
export interface IOP721 {
|
|
5
|
+
// Core NFT properties
|
|
6
|
+
fn_name(calldata: Calldata): BytesWriter;
|
|
7
|
+
fn_symbol(calldata: Calldata): BytesWriter;
|
|
8
|
+
tokenURI(calldata: Calldata): BytesWriter;
|
|
9
|
+
fn_totalSupply(calldata: Calldata): BytesWriter;
|
|
10
|
+
|
|
11
|
+
// Balance and ownership
|
|
12
|
+
balanceOf(calldata: Calldata): BytesWriter;
|
|
13
|
+
ownerOf(calldata: Calldata): BytesWriter;
|
|
14
|
+
|
|
15
|
+
// Transfer functions
|
|
16
|
+
transferFrom(calldata: Calldata): BytesWriter;
|
|
17
|
+
safeTransferFrom(calldata: Calldata): BytesWriter;
|
|
18
|
+
|
|
19
|
+
// Approval functions
|
|
20
|
+
approve(calldata: Calldata): BytesWriter;
|
|
21
|
+
getApproved(calldata: Calldata): BytesWriter;
|
|
22
|
+
setApprovalForAll(calldata: Calldata): BytesWriter;
|
|
23
|
+
isApprovedForAll(calldata: Calldata): BytesWriter;
|
|
24
|
+
|
|
25
|
+
// Advanced functions
|
|
26
|
+
burn(calldata: Calldata): BytesWriter;
|
|
27
|
+
transferBySignature(calldata: Calldata): BytesWriter;
|
|
28
|
+
domainSeparator(calldata: Calldata): BytesWriter;
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
2
|
+
|
|
3
|
+
export class OP721InitParameters {
|
|
4
|
+
public name: string;
|
|
5
|
+
public symbol: string;
|
|
6
|
+
public baseURI: string;
|
|
7
|
+
public maxSupply: u256;
|
|
8
|
+
|
|
9
|
+
constructor(name: string, symbol: string, baseURI: string, maxSupply: u256) {
|
|
10
|
+
this.name = name;
|
|
11
|
+
this.symbol = symbol;
|
|
12
|
+
this.baseURI = baseURI;
|
|
13
|
+
this.maxSupply = maxSupply;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -35,6 +35,14 @@ import { Network, Networks } from '../script/Networks';
|
|
|
35
35
|
|
|
36
36
|
export * from '../env/global';
|
|
37
37
|
|
|
38
|
+
@final
|
|
39
|
+
export class CallResult {
|
|
40
|
+
constructor(
|
|
41
|
+
public readonly success: boolean,
|
|
42
|
+
public readonly data: BytesReader,
|
|
43
|
+
) {}
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
@final
|
|
39
47
|
export class BlockchainEnvironment {
|
|
40
48
|
public readonly DEAD_ADDRESS: Address = Address.dead();
|
|
@@ -204,7 +212,11 @@ export class BlockchainEnvironment {
|
|
|
204
212
|
this.createContractIfNotExists();
|
|
205
213
|
}
|
|
206
214
|
|
|
207
|
-
public call(
|
|
215
|
+
public call(
|
|
216
|
+
destinationContract: Address,
|
|
217
|
+
calldata: BytesWriter,
|
|
218
|
+
stopExecutionOnFailure: boolean = true,
|
|
219
|
+
): CallResult {
|
|
208
220
|
if (!destinationContract) {
|
|
209
221
|
throw new Revert('Destination contract is required');
|
|
210
222
|
}
|
|
@@ -222,11 +234,11 @@ export class BlockchainEnvironment {
|
|
|
222
234
|
const resultBuffer = new ArrayBuffer(resultLength);
|
|
223
235
|
getCallResult(0, resultLength, resultBuffer);
|
|
224
236
|
|
|
225
|
-
if (status !== 0) {
|
|
237
|
+
if (status !== 0 && stopExecutionOnFailure) {
|
|
226
238
|
env_exit(status, resultBuffer, resultLength);
|
|
227
239
|
}
|
|
228
240
|
|
|
229
|
-
return new BytesReader(Uint8Array.wrap(resultBuffer));
|
|
241
|
+
return new CallResult(status === 0, new BytesReader(Uint8Array.wrap(resultBuffer)));
|
|
230
242
|
}
|
|
231
243
|
|
|
232
244
|
public log(data: string): void {
|
|
@@ -285,6 +297,7 @@ export class BlockchainEnvironment {
|
|
|
285
297
|
|
|
286
298
|
public getStorageAt(pointerHash: Uint8Array): Uint8Array {
|
|
287
299
|
this.hasPointerStorageHash(pointerHash);
|
|
300
|
+
|
|
288
301
|
if (this.storage.has(pointerHash)) {
|
|
289
302
|
return this.storage.get(pointerHash);
|
|
290
303
|
}
|
|
@@ -367,12 +380,20 @@ export class BlockchainEnvironment {
|
|
|
367
380
|
private _internalSetStorageAt(pointerHash: Uint8Array, value: Uint8Array): void {
|
|
368
381
|
this.storage.set(pointerHash, value);
|
|
369
382
|
|
|
383
|
+
if (pointerHash.buffer.byteLength !== 32 || value.buffer.byteLength !== 32) {
|
|
384
|
+
throw new Revert('Pointer and value must be 32 bytes long');
|
|
385
|
+
}
|
|
386
|
+
|
|
370
387
|
storePointer(pointerHash.buffer, value.buffer);
|
|
371
388
|
}
|
|
372
389
|
|
|
373
390
|
private _internalSetTransientStorageAt(pointerHash: Uint8Array, value: Uint8Array): void {
|
|
374
391
|
this.transientStorage.set(pointerHash, value);
|
|
375
392
|
|
|
393
|
+
if (pointerHash.buffer.byteLength !== 32 || value.buffer.byteLength !== 32) {
|
|
394
|
+
throw new Revert('Transient pointer and value must be 32 bytes long');
|
|
395
|
+
}
|
|
396
|
+
|
|
376
397
|
tStorePointer(pointerHash.buffer, value.buffer);
|
|
377
398
|
}
|
|
378
399
|
|
|
@@ -381,6 +402,10 @@ export class BlockchainEnvironment {
|
|
|
381
402
|
return true;
|
|
382
403
|
}
|
|
383
404
|
|
|
405
|
+
if (pointer.buffer.byteLength !== 32) {
|
|
406
|
+
throw new Revert('Pointer must be 32 bytes long');
|
|
407
|
+
}
|
|
408
|
+
|
|
384
409
|
// we attempt to load the requested pointer.
|
|
385
410
|
const resultBuffer = new ArrayBuffer(32);
|
|
386
411
|
loadPointer(pointer.buffer, resultBuffer);
|
|
@@ -396,6 +421,10 @@ export class BlockchainEnvironment {
|
|
|
396
421
|
return true;
|
|
397
422
|
}
|
|
398
423
|
|
|
424
|
+
if (pointer.buffer.byteLength !== 32) {
|
|
425
|
+
throw new Revert('Transient pointer must be 32 bytes long');
|
|
426
|
+
}
|
|
427
|
+
|
|
399
428
|
// we attempt to load the requested pointer.
|
|
400
429
|
const resultBuffer = new ArrayBuffer(32);
|
|
401
430
|
tLoadPointer(pointer.buffer, resultBuffer);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NetEvent } from '../NetEvent';
|
|
2
|
+
import { BytesWriter } from '../../buffer/BytesWriter';
|
|
3
|
+
import { Address } from '../../types/Address';
|
|
4
|
+
import { ADDRESS_BYTE_LENGTH } from '../../utils';
|
|
5
|
+
|
|
6
|
+
@final
|
|
7
|
+
export class ApprovedForAllEvent extends NetEvent {
|
|
8
|
+
constructor(account: Address, operator: Address, approved: boolean) {
|
|
9
|
+
const writer = new BytesWriter(ADDRESS_BYTE_LENGTH * 2 + 1);
|
|
10
|
+
writer.writeAddress(account);
|
|
11
|
+
writer.writeAddress(operator);
|
|
12
|
+
writer.writeBoolean(approved);
|
|
13
|
+
|
|
14
|
+
super('ApprovedForAll', writer);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NetEvent } from '../NetEvent';
|
|
2
|
+
import { Address } from '../../types/Address';
|
|
3
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
4
|
+
import { ADDRESS_BYTE_LENGTH, U256_BYTE_LENGTH, U32_BYTE_LENGTH } from '../../utils';
|
|
5
|
+
import { BytesWriter } from '../../buffer/BytesWriter';
|
|
6
|
+
import { Revert } from '../../types/Revert';
|
|
7
|
+
|
|
8
|
+
@final
|
|
9
|
+
export class TransferredBatchEvent extends NetEvent {
|
|
10
|
+
constructor(operator: Address, from: Address, to: Address, ids: u256[], values: u256[]) {
|
|
11
|
+
// Check max array size to avoid exceeding event data limit
|
|
12
|
+
// 3 addresses (32*3) + 2 lengths (4*2) = 104 bytes overhead
|
|
13
|
+
// Each id+value pair = 64 bytes
|
|
14
|
+
// Max pairs = (352 - 104) / 64 = 3.875, so max 3 items
|
|
15
|
+
if (ids.length > 3) {
|
|
16
|
+
throw new Revert('TransferBatch event exceeds max data size');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const size =
|
|
20
|
+
ADDRESS_BYTE_LENGTH * 3 + U32_BYTE_LENGTH * 2 + ids.length * U256_BYTE_LENGTH * 2;
|
|
21
|
+
const writer = new BytesWriter(size);
|
|
22
|
+
writer.writeAddress(operator);
|
|
23
|
+
writer.writeAddress(from);
|
|
24
|
+
writer.writeAddress(to);
|
|
25
|
+
writer.writeU32(u32(ids.length));
|
|
26
|
+
for (let i = 0; i < ids.length; i++) {
|
|
27
|
+
writer.writeU256(ids[i]);
|
|
28
|
+
}
|
|
29
|
+
writer.writeU32(u32(values.length));
|
|
30
|
+
for (let i = 0; i < values.length; i++) {
|
|
31
|
+
writer.writeU256(values[i]);
|
|
32
|
+
}
|
|
33
|
+
super('TransferredBatch', writer);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NetEvent } from '../NetEvent';
|
|
2
|
+
import { Address } from '../../types/Address';
|
|
3
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
4
|
+
import { BytesWriter } from '../../buffer/BytesWriter';
|
|
5
|
+
import { ADDRESS_BYTE_LENGTH, U256_BYTE_LENGTH } from '../../utils';
|
|
6
|
+
|
|
7
|
+
@final
|
|
8
|
+
export class TransferredSingleEvent extends NetEvent {
|
|
9
|
+
constructor(operator: Address, from: Address, to: Address, id: u256, value: u256) {
|
|
10
|
+
const writer = new BytesWriter(ADDRESS_BYTE_LENGTH * 3 + U256_BYTE_LENGTH * 2);
|
|
11
|
+
writer.writeAddress(operator);
|
|
12
|
+
writer.writeAddress(from);
|
|
13
|
+
writer.writeAddress(to);
|
|
14
|
+
writer.writeU256(id);
|
|
15
|
+
writer.writeU256(value);
|
|
16
|
+
super('TransferredSingle', writer);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { NetEvent } from '../NetEvent';
|
|
2
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
3
|
+
import { BytesWriter } from '../../buffer/BytesWriter';
|
|
4
|
+
import { U256_BYTE_LENGTH, U32_BYTE_LENGTH } from '../../utils';
|
|
5
|
+
import { Revert } from '../../types/Revert';
|
|
6
|
+
|
|
7
|
+
export const MAX_URI_LENGTH: u32 = 200;
|
|
8
|
+
|
|
9
|
+
@final
|
|
10
|
+
export class URIEvent extends NetEvent {
|
|
11
|
+
constructor(value: string, id: u256) {
|
|
12
|
+
const valueBytes: i32 = String.UTF8.byteLength(value);
|
|
13
|
+
|
|
14
|
+
if (valueBytes > MAX_URI_LENGTH) {
|
|
15
|
+
throw new Revert('URI event exceeds max data size');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const writer = new BytesWriter(U32_BYTE_LENGTH + valueBytes + U256_BYTE_LENGTH);
|
|
19
|
+
writer.writeStringWithLength(value);
|
|
20
|
+
writer.writeU256(id);
|
|
21
|
+
super('URI', writer);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -2,3 +2,7 @@ export * from './ApprovedEvent';
|
|
|
2
2
|
export * from './BurnedEvent';
|
|
3
3
|
export * from './MintedEvent';
|
|
4
4
|
export * from './TransferredEvent';
|
|
5
|
+
export * from './ApprovedForAll';
|
|
6
|
+
export * from './URIEvent';
|
|
7
|
+
export * from './TransferredBatchEvent';
|
|
8
|
+
export * from './TransferredSingleEvent';
|
package/runtime/index.ts
CHANGED
|
@@ -97,3 +97,12 @@ export * from './script/BitcoinAddresses';
|
|
|
97
97
|
export * from './script/Networks';
|
|
98
98
|
export * from './script/Opcodes';
|
|
99
99
|
export * from './script/Segwit';
|
|
100
|
+
|
|
101
|
+
export * from './constants/Exports';
|
|
102
|
+
export * from './contracts/OP721';
|
|
103
|
+
export * from './contracts/interfaces/IOP721';
|
|
104
|
+
export * from './contracts/OP1155';
|
|
105
|
+
export * from './contracts/interfaces/IOP1155';
|
|
106
|
+
export * from './contracts/interfaces/OP721InitParameters';
|
|
107
|
+
export * from './contracts/ReentrancyGuard';
|
|
108
|
+
export * from './contracts/interfaces/OP1155InitParameters';
|
package/runtime/math/abi.ts
CHANGED
|
@@ -68,11 +68,34 @@ export function u256To30Bytes(value: u256): Uint8Array {
|
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* Optimized pointer encoding, see encodePointerUnknownLength for a more generic version.
|
|
71
|
+
*
|
|
72
|
+
* ⚠️ CRITICAL SECURITY NOTICE ⚠️
|
|
73
|
+
* ================================================================================================
|
|
74
|
+
* THIS FUNCTION WILL OVERWRITE THE FIRST 2 BYTES OF YOUR DATA!
|
|
75
|
+
*
|
|
76
|
+
* If you pass a 32-byte buffer, the first 2 bytes WILL BE DESTROYED and replaced with the
|
|
77
|
+
* uniqueIdentifier. This is BY DESIGN for pointer encoding.
|
|
78
|
+
*
|
|
79
|
+
* CORRECT USAGE:
|
|
80
|
+
* - For incremental pointers: You MUST provide exactly 30 bytes of data, NOT 32 bytes
|
|
81
|
+
* - The function will create a new 32-byte buffer with:
|
|
82
|
+
* - Bytes 0-1: uniqueIdentifier (split into two bytes)
|
|
83
|
+
* - Bytes 2-31: Your 30 bytes of data
|
|
84
|
+
*
|
|
85
|
+
* INCORRECT USAGE (DATA LOSS):
|
|
86
|
+
* - DO NOT pass 32 bytes expecting them to be preserved
|
|
87
|
+
* - DO NOT assume this function appends the identifier - it PREPENDS and shifts your data
|
|
88
|
+
*
|
|
89
|
+
* Example:
|
|
90
|
+
* - Input: uniqueIdentifier=0x1234, data=[30 bytes]
|
|
91
|
+
* - Output: [0x34, 0x12, ...your 30 bytes...] = 32 bytes total
|
|
92
|
+
*
|
|
71
93
|
* @param uniqueIdentifier
|
|
72
94
|
* @param typed
|
|
73
95
|
* @param enforce30Bytes
|
|
74
96
|
* @param {string} context Optional debug context.
|
|
75
97
|
*/
|
|
98
|
+
@unsafe
|
|
76
99
|
export function encodePointer(uniqueIdentifier: u16, typed: Uint8Array, enforce30Bytes: boolean = true, context: string = ''): Uint8Array {
|
|
77
100
|
const array = ensureAtLeast30Bytes(typed);
|
|
78
101
|
|
package/runtime/math/bytes.ts
CHANGED
|
@@ -159,6 +159,10 @@ export function writeLengthAndStartIndex(length: u32, startIndex: u32): Uint8Arr
|
|
|
159
159
|
|
|
160
160
|
@inline
|
|
161
161
|
export function bigEndianAdd(base: Uint8Array, increment: u64): Uint8Array {
|
|
162
|
+
if(base.length !== 32) {
|
|
163
|
+
throw new Revert('bigEndianAdd: base must be 32 bytes');
|
|
164
|
+
}
|
|
165
|
+
|
|
162
166
|
const add = u64ToBE32Bytes(increment);
|
|
163
167
|
|
|
164
168
|
return addUint8ArraysBE(base, add);
|
|
@@ -5,6 +5,26 @@ import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
|
5
5
|
import { EMPTY_BUFFER } from '../math/bytes';
|
|
6
6
|
import { Revert } from '../types/Revert';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* SECURITY NOTICE:
|
|
10
|
+
*
|
|
11
|
+
* This class uses a 30-byte truncation of addresses for storage pointer generation.
|
|
12
|
+
* While this may appear to introduce collision risks, it is secure within the OP_NET
|
|
13
|
+
* protocol context because:
|
|
14
|
+
*
|
|
15
|
+
* 1. All addresses in OP_NET are tweaked public keys (32-byte elliptic curve points)
|
|
16
|
+
*
|
|
17
|
+
* 2. Tweaked public keys are uniformly distributed across the secp256k1 curve space
|
|
18
|
+
*
|
|
19
|
+
* 3. Finding two public keys with identical 30-byte prefixes (240 bits) requires
|
|
20
|
+
* approximately 2^120 operations due to the birthday paradox
|
|
21
|
+
*
|
|
22
|
+
* 4. The probability of accidentally generating colliding addresses through normal
|
|
23
|
+
* key generation is cryptographically negligible
|
|
24
|
+
*
|
|
25
|
+
* The truncation from 32 to 30 bytes is a space optimization that does not
|
|
26
|
+
* meaningfully impact security given the uniform distribution of elliptic curve points.
|
|
27
|
+
*/
|
|
8
28
|
@final
|
|
9
29
|
export class AddressMemoryMap {
|
|
10
30
|
public pointer: u16;
|
|
@@ -14,6 +14,7 @@ import { U256Codec } from '../codecs/U256Codec';
|
|
|
14
14
|
|
|
15
15
|
import { Address } from '../../types/Address';
|
|
16
16
|
import { Revert } from '../../types/Revert';
|
|
17
|
+
import { EMPTY_BUFFER } from '../../math/bytes';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* A reflection-based StorageMap<K, V>.
|
|
@@ -32,10 +33,6 @@ export class StorageMap<K, V> {
|
|
|
32
33
|
this.pointer = pointer;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
// ----------------------------------------------------------
|
|
36
|
-
// PUBLIC API
|
|
37
|
-
// ----------------------------------------------------------
|
|
38
|
-
|
|
39
36
|
public set(key: K, value: V): this {
|
|
40
37
|
const storageKey = this.getStorageKey(key);
|
|
41
38
|
this.storeValue<V>(storageKey, value);
|
|
@@ -59,7 +56,7 @@ export class StorageMap<K, V> {
|
|
|
59
56
|
return false;
|
|
60
57
|
}
|
|
61
58
|
|
|
62
|
-
Blockchain.setStorageAt(storageKey,
|
|
59
|
+
Blockchain.setStorageAt(storageKey, EMPTY_BUFFER);
|
|
63
60
|
return true;
|
|
64
61
|
}
|
|
65
62
|
|
|
@@ -69,22 +66,12 @@ export class StorageMap<K, V> {
|
|
|
69
66
|
throw new Revert('clear() not implemented; no key-tracking logic here.');
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
// ----------------------------------------------------------
|
|
73
|
-
// INTERNAL: Derive the final storage key
|
|
74
|
-
// ----------------------------------------------------------
|
|
75
|
-
|
|
76
69
|
private getStorageKey(k: K): Uint8Array {
|
|
77
|
-
// 1) encode the key
|
|
78
70
|
const keyBytes = this.encodeValue<K>(k);
|
|
79
71
|
|
|
80
|
-
// 2) transform with pointer
|
|
81
72
|
return encodePointerUnknownLength(this.pointer, keyBytes);
|
|
82
73
|
}
|
|
83
74
|
|
|
84
|
-
// ----------------------------------------------------------
|
|
85
|
-
// ENCODE / DECODE
|
|
86
|
-
// ----------------------------------------------------------
|
|
87
|
-
|
|
88
75
|
/**
|
|
89
76
|
* Retrieve the value of type P from the given storage pointer (32 bytes).
|
|
90
77
|
* If it's a variable-length type, we decode from pointer-based approach
|
|
@@ -123,10 +110,6 @@ export class StorageMap<K, V> {
|
|
|
123
110
|
Blockchain.setStorageAt(storageKey, raw);
|
|
124
111
|
}
|
|
125
112
|
|
|
126
|
-
// ----------------------------------------------------------
|
|
127
|
-
// decodeBytesAsType: interpret raw bytes as type P
|
|
128
|
-
// ----------------------------------------------------------
|
|
129
|
-
|
|
130
113
|
private decodeBytesAsType<P>(raw: Uint8Array): P {
|
|
131
114
|
// isInteger => built-in numeric types (i32, u32, etc.)
|
|
132
115
|
if (isInteger<P>()) {
|
|
@@ -173,10 +156,6 @@ export class StorageMap<K, V> {
|
|
|
173
156
|
throw new Revert(`Unsupported type ${typeId}`);
|
|
174
157
|
}
|
|
175
158
|
|
|
176
|
-
// ----------------------------------------------------------
|
|
177
|
-
// encodeValue<P>: convert a value of type P into raw bytes
|
|
178
|
-
// ----------------------------------------------------------
|
|
179
|
-
|
|
180
159
|
private encodeValue<P>(value: P): Uint8Array {
|
|
181
160
|
// built-in integer => write with BytesWriter
|
|
182
161
|
if (isInteger<P>()) {
|
|
@@ -221,6 +200,7 @@ export class StorageMap<K, V> {
|
|
|
221
200
|
|
|
222
201
|
// If nested map => handle in a custom branch (not shown here):
|
|
223
202
|
// if (value instanceof StorageMap<...>) { ... }
|
|
203
|
+
// TODO
|
|
224
204
|
|
|
225
205
|
throw new Revert('encodeValue: Unsupported type');
|
|
226
206
|
}
|
|
@@ -4,6 +4,9 @@ import { encodePointerUnknownLength } from '../../math/abi';
|
|
|
4
4
|
import { Blockchain } from '../../env';
|
|
5
5
|
import { Revert } from '../../types/Revert';
|
|
6
6
|
|
|
7
|
+
const SLOT_BYTES: i32 = 32;
|
|
8
|
+
const PRESENCE_BYTE: u8 = 1;
|
|
9
|
+
|
|
7
10
|
export class StorageSet<T> {
|
|
8
11
|
private readonly pointer: u16;
|
|
9
12
|
private readonly parentKey: Uint8Array;
|
|
@@ -20,8 +23,8 @@ export class StorageSet<T> {
|
|
|
20
23
|
const storageKey = this.getStorageKey(value);
|
|
21
24
|
|
|
22
25
|
// A 1-byte array with a single 0x01 is enough to mark presence
|
|
23
|
-
const flag = new Uint8Array(
|
|
24
|
-
flag[31] =
|
|
26
|
+
const flag = new Uint8Array(SLOT_BYTES);
|
|
27
|
+
flag[31] = PRESENCE_BYTE;
|
|
25
28
|
|
|
26
29
|
Blockchain.setStorageAt(storageKey, flag);
|
|
27
30
|
}
|
|
@@ -37,7 +40,7 @@ export class StorageSet<T> {
|
|
|
37
40
|
return false;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
Blockchain.setStorageAt(storageKey, new Uint8Array(
|
|
43
|
+
Blockchain.setStorageAt(storageKey, new Uint8Array(SLOT_BYTES));
|
|
41
44
|
return true;
|
|
42
45
|
}
|
|
43
46
|
|
package/runtime/script/Script.ts
CHANGED
|
@@ -64,7 +64,7 @@ export class ScriptNumber {
|
|
|
64
64
|
/**
|
|
65
65
|
* Decode result type for safe error handling
|
|
66
66
|
*/
|
|
67
|
-
static decodeResult(data: Uint8Array, minimal: bool = true): DecodeNumberResult {
|
|
67
|
+
public static decodeResult(data: Uint8Array, minimal: bool = true): DecodeNumberResult {
|
|
68
68
|
const L = data.length;
|
|
69
69
|
if (L == 0) return DecodeNumberResult.ok(0);
|
|
70
70
|
|
|
@@ -41,7 +41,7 @@ export class ECPoint {
|
|
|
41
41
|
// x3 = λ^2 - 2x mod P
|
|
42
42
|
// y3 = λ*(x - x3) - y mod P
|
|
43
43
|
// ----------------------------
|
|
44
|
-
static double(p: ECPoint): ECPoint {
|
|
44
|
+
public static double(p: ECPoint): ECPoint {
|
|
45
45
|
// If y=0, return infinity
|
|
46
46
|
if (p.y == u256.Zero) {
|
|
47
47
|
return new ECPoint(u256.Zero, u256.Zero); // "Point at infinity" convention
|
|
@@ -80,7 +80,7 @@ export class ECPoint {
|
|
|
80
80
|
// x3 = λ^2 - x1 - x2 mod P
|
|
81
81
|
// y3 = λ*(x1 - x3) - y1 mod P
|
|
82
82
|
// ----------------------------
|
|
83
|
-
static add(p: ECPoint, q: ECPoint): ECPoint {
|
|
83
|
+
public static add(p: ECPoint, q: ECPoint): ECPoint {
|
|
84
84
|
// 1) Check for infinity cases
|
|
85
85
|
const isPInfinity = p.x.isZero() && p.y.isZero();
|
|
86
86
|
const isQInfinity = q.x.isZero() && q.y.isZero();
|
|
@@ -122,7 +122,7 @@ export class ECPoint {
|
|
|
122
122
|
// Scalar Multiplication: k*P
|
|
123
123
|
// Double-and-add approach
|
|
124
124
|
// ----------------------------
|
|
125
|
-
static scalarMultiply(p: ECPoint, k: u256): ECPoint {
|
|
125
|
+
public static scalarMultiply(p: ECPoint, k: u256): ECPoint {
|
|
126
126
|
let result = new ECPoint(u256.Zero, u256.Zero); // ∞
|
|
127
127
|
let addend = p;
|
|
128
128
|
const two = u256.fromU64(2);
|