@btc-vision/btc-runtime 1.9.2 → 1.9.4

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.
@@ -35,6 +35,10 @@ import { Network, Networks } from '../script/Networks';
35
35
 
36
36
  export * from '../env/global';
37
37
 
38
+ /**
39
+ * CallResult encapsulates the outcome of a cross-contract call.
40
+ * Contains both a success flag and the response data, enabling try-catch patterns.
41
+ */
38
42
  @final
39
43
  export class CallResult {
40
44
  constructor(
@@ -43,146 +47,287 @@ export class CallResult {
43
47
  ) {}
44
48
  }
45
49
 
50
+ /**
51
+ * BlockchainEnvironment - Core Runtime Environment for OP_NET Smart Contracts
52
+ *
53
+ * Provides the interface between smart contracts and the blockchain runtime,
54
+ * managing storage, cross-contract calls, cryptographic operations, and execution context.
55
+ *
56
+ * @module BlockchainEnvironment
57
+ */
46
58
  @final
47
59
  export class BlockchainEnvironment {
60
+ /**
61
+ * Standard dead address for burn operations.
62
+ * Assets sent here are permanently unrecoverable.
63
+ */
48
64
  public readonly DEAD_ADDRESS: Address = Address.dead();
49
65
 
50
66
  private storage: MapUint8Array = new MapUint8Array();
51
67
  private transientStorage: MapUint8Array = new MapUint8Array();
52
68
  private _selfContract: Potential<OP_NET> = null;
53
69
  private _plugins: Plugin[] = [];
54
-
55
70
  private _network: Networks = Networks.Unknown;
56
71
 
72
+ /**
73
+ * Returns the current blockchain network identifier.
74
+ *
75
+ * @returns The network enum value (Mainnet, Testnet, etc.)
76
+ * @throws {Revert} When network is not initialized
77
+ *
78
+ * @remarks
79
+ * Determines address validation rules and network-specific constants.
80
+ */
57
81
  @inline
58
82
  public get network(): Networks {
59
83
  if (this._network === Networks.Unknown) {
60
84
  throw new Revert('Network is required');
61
85
  }
62
-
63
86
  return this._network as Networks;
64
87
  }
65
88
 
66
89
  private _block: Potential<Block> = null;
67
90
 
91
+ /**
92
+ * Provides access to current block information.
93
+ *
94
+ * @returns Block object containing hash, number, and median time
95
+ * @throws {Revert} When block context is not initialized
96
+ *
97
+ * @warning Block timestamps can vary ±900 seconds. Use median time for reliability.
98
+ * Never use block properties for randomness generation.
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * const currentBlock = Blockchain.block;
103
+ * const blockNumber = currentBlock.number;
104
+ * const blockTime = currentBlock.medianTime;
105
+ * ```
106
+ */
68
107
  @inline
69
108
  public get block(): Block {
70
109
  if (!this._block) {
71
110
  throw new Revert('Block is required');
72
111
  }
73
-
74
112
  return this._block as Block;
75
113
  }
76
114
 
77
115
  private _tx: Potential<Transaction> = null;
78
116
 
117
+ /**
118
+ * Provides access to current transaction information.
119
+ *
120
+ * @returns Transaction object with caller, origin, and tx identifiers
121
+ * @throws {Revert} When transaction context is not initialized
122
+ *
123
+ * @warning tx.caller = immediate calling contract (changes in call chain)
124
+ * tx.origin = original transaction initiator (stays constant)
125
+ * Using tx.origin for authentication is usually wrong.
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * const tx = Blockchain.tx;
130
+ * const directCaller = tx.caller; // Who called this function
131
+ * const txInitiator = tx.origin; // Who started the transaction
132
+ * ```
133
+ */
79
134
  @inline
80
135
  public get tx(): Transaction {
81
136
  if (!this._tx) {
82
137
  throw new Revert('Transaction is required');
83
138
  }
84
-
85
139
  return this._tx as Transaction;
86
140
  }
87
141
 
88
142
  private _contract: Potential<() => OP_NET> = null;
89
143
 
144
+ /**
145
+ * Returns the current contract instance.
146
+ *
147
+ * @returns The initialized OP_NET contract
148
+ */
90
149
  public get contract(): OP_NET {
91
150
  return this._selfContract as OP_NET;
92
151
  }
93
152
 
153
+ /**
154
+ * Sets the contract factory function for lazy initialization.
155
+ *
156
+ * @param contract - Factory function that creates the contract instance
157
+ */
94
158
  public set contract(contract: () => OP_NET) {
95
159
  this._contract = contract;
96
-
97
160
  this.createContractIfNotExists();
98
161
  }
99
162
 
100
163
  private _nextPointer: u16 = 0;
101
164
 
165
+ /**
166
+ * Generates the next available storage pointer.
167
+ *
168
+ * @returns Unique pointer value for storage allocation
169
+ * @throws {Revert} When pointer space is exhausted (after 65,535 allocations)
170
+ *
171
+ * @warning Limited to 65,535 storage slots per contract.
172
+ * Use mappings for dynamic data to avoid exhaustion.
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * const ptr = Blockchain.nextPointer; // Gets next unique pointer
177
+ * ```
178
+ */
102
179
  public get nextPointer(): u16 {
103
180
  if (this._nextPointer === u16.MAX_VALUE) {
104
181
  throw new Revert(`Out of storage pointer.`);
105
182
  }
106
-
107
183
  this._nextPointer += 1;
108
-
109
184
  return this._nextPointer;
110
185
  }
111
186
 
112
187
  public _contractDeployer: Potential<Address> = null;
113
188
 
189
+ /**
190
+ * Returns the address that deployed this contract.
191
+ *
192
+ * @returns Deployer's address
193
+ * @throws {Revert} When deployer is not set
194
+ *
195
+ * @remarks
196
+ * Immutable after deployment. Often used for admin privileges.
197
+ */
114
198
  public get contractDeployer(): Address {
115
199
  if (!this._contractDeployer) {
116
200
  throw new Revert('Deployer is required');
117
201
  }
118
-
119
202
  return this._contractDeployer as Address;
120
203
  }
121
204
 
122
205
  public _contractAddress: Potential<Address> = null;
123
206
 
207
+ /**
208
+ * Returns this contract's own address.
209
+ *
210
+ * @returns Current contract address
211
+ * @throws {Revert} When address is not initialized
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * const selfAddress = Blockchain.contractAddress;
216
+ * if (caller.equals(selfAddress)) {
217
+ * // Recursive call detected
218
+ * }
219
+ * ```
220
+ */
124
221
  public get contractAddress(): Address {
125
222
  if (!this._contractAddress) {
126
223
  throw new Revert('Contract address is required');
127
224
  }
128
-
129
225
  return this._contractAddress as Address;
130
226
  }
131
227
 
132
228
  public _chainId: Potential<Uint8Array> = null;
133
229
 
230
+ /**
231
+ * Returns the blockchain's unique chain identifier.
232
+ *
233
+ * @returns 32-byte chain ID
234
+ * @throws {Revert} When chain ID is not set
235
+ *
236
+ * @remarks
237
+ * Used for replay protection and cross-chain message verification.
238
+ */
134
239
  public get chainId(): Uint8Array {
135
240
  if (!this._chainId) {
136
241
  throw new Revert('Chain id is required');
137
242
  }
138
-
139
243
  return this._chainId as Uint8Array;
140
244
  }
141
245
 
142
246
  public _protocolId: Potential<Uint8Array> = null;
143
247
 
248
+ /**
249
+ * Returns the protocol version identifier.
250
+ *
251
+ * @returns 32-byte protocol ID
252
+ * @throws {Revert} When protocol ID is not set
253
+ */
144
254
  public get protocolId(): Uint8Array {
145
255
  if (!this._protocolId) {
146
256
  throw new Revert('Protocol id is required');
147
257
  }
148
-
149
258
  return this._protocolId as Uint8Array;
150
259
  }
151
260
 
261
+ /**
262
+ * Registers a plugin to extend contract functionality.
263
+ *
264
+ * @param plugin - Plugin instance to register
265
+ *
266
+ * @remarks
267
+ * Plugins execute in registration order and have full access to contract state.
268
+ */
152
269
  public registerPlugin(plugin: Plugin): void {
153
270
  this._plugins.push(plugin);
154
271
  }
155
272
 
273
+ /**
274
+ * Handles contract deployment initialization.
275
+ *
276
+ * @param calldata - Deployment parameters
277
+ *
278
+ * @remarks
279
+ * Called once during deployment. State changes here are permanent.
280
+ */
156
281
  public onDeployment(calldata: Calldata): void {
157
282
  for (let i: i32 = 0; i < this._plugins.length; i++) {
158
283
  const plugin = this._plugins[i];
159
-
160
284
  plugin.onDeployment(calldata);
161
285
  }
162
-
163
286
  this.contract.onDeployment(calldata);
164
287
  }
165
288
 
289
+ /**
290
+ * Pre-execution hook called before method execution.
291
+ *
292
+ * @param selector - Method selector being called
293
+ * @param calldata - Method parameters
294
+ *
295
+ * @remarks
296
+ * Used for access control, reentrancy guards, and validation.
297
+ */
166
298
  public onExecutionStarted(selector: Selector, calldata: Calldata): void {
167
299
  for (let i: i32 = 0; i < this._plugins.length; i++) {
168
300
  const plugin = this._plugins[i];
169
-
170
301
  plugin.onExecutionStarted(selector, calldata);
171
302
  }
172
-
173
303
  this.contract.onExecutionStarted(selector, calldata);
174
304
  }
175
305
 
306
+ /**
307
+ * Post-execution hook called after successful method execution.
308
+ *
309
+ * @param selector - Method selector that was called
310
+ * @param calldata - Method parameters that were passed
311
+ *
312
+ * @remarks
313
+ * Only called on successful execution. Used for cleanup and events.
314
+ */
176
315
  public onExecutionCompleted(selector: Selector, calldata: Calldata): void {
177
316
  for (let i: i32 = 0; i < this._plugins.length; i++) {
178
317
  const plugin = this._plugins[i];
179
-
180
318
  plugin.onExecutionCompleted(selector, calldata);
181
319
  }
182
-
183
320
  this.contract.onExecutionCompleted(selector, calldata);
184
321
  }
185
322
 
323
+ /**
324
+ * Initializes the blockchain environment with runtime parameters.
325
+ *
326
+ * @param data - Encoded environment data from the runtime
327
+ *
328
+ * @remarks
329
+ * Called automatically by the runtime to set up execution context.
330
+ */
186
331
  public setEnvironmentVariables(data: Uint8Array): void {
187
332
  const reader: BytesReader = new BytesReader(data);
188
333
 
@@ -199,19 +344,50 @@ export class BlockchainEnvironment {
199
344
  const protocolId = reader.readBytes(32);
200
345
 
201
346
  this._tx = new Transaction(caller, origin, txId, txHash);
202
-
203
347
  this._contractDeployer = contractDeployer;
204
348
  this._contractAddress = contractAddress;
205
349
  this._chainId = chainId;
206
350
  this._protocolId = protocolId;
207
-
208
351
  this._network = Network.fromChainId(this.chainId);
209
-
210
352
  this._block = new Block(blockHash, blockNumber, blockMedianTime);
211
353
 
212
354
  this.createContractIfNotExists();
213
355
  }
214
356
 
357
+ /**
358
+ * Executes a call to another contract with configurable failure handling.
359
+ *
360
+ * @param destinationContract - Target contract address
361
+ * @param calldata - Encoded function call data
362
+ * @param stopExecutionOnFailure - Whether to revert on call failure (default: true)
363
+ * @returns CallResult with success flag and response data
364
+ *
365
+ * @example
366
+ * ```typescript
367
+ * // Traditional call - reverts on failure
368
+ * const result = Blockchain.call(tokenAddress, calldata);
369
+ * const balance = result.data.readU256();
370
+ *
371
+ * // Try-catch pattern - handles failure gracefully
372
+ * const result = Blockchain.call(unknownContract, calldata, false);
373
+ * if (result.success) {
374
+ * const data = result.data;
375
+ * // Process successful response
376
+ * } else {
377
+ * // Handle failure without reverting
378
+ * this.handleCallFailure();
379
+ * }
380
+ * ```
381
+ *
382
+ * @warning Follow checks-effects-interactions pattern to prevent reentrancy:
383
+ * 1. Check conditions
384
+ * 2. Update state
385
+ * 3. Make external call
386
+ *
387
+ * @remarks
388
+ * The stopExecutionOnFailure parameter enables try-catch style error handling.
389
+ * When false, failed calls return success=false instead of reverting.
390
+ */
215
391
  public call(
216
392
  destinationContract: Address,
217
393
  calldata: BytesWriter,
@@ -241,6 +417,22 @@ export class BlockchainEnvironment {
241
417
  return new CallResult(status === 0, new BytesReader(Uint8Array.wrap(resultBuffer)));
242
418
  }
243
419
 
420
+ /**
421
+ * Emits a log message for debugging.
422
+ *
423
+ * @param data - String message to log
424
+ *
425
+ * @warning ONLY AVAILABLE IN UNIT TESTING FRAMEWORK.
426
+ * NOT available in production or testnet environments.
427
+ * Will fail if called outside of testing context.
428
+ *
429
+ * @example
430
+ * ```typescript
431
+ * // Only in unit tests:
432
+ * Blockchain.log("Debug: Transfer initiated");
433
+ * Blockchain.log(`Amount: ${amount.toString()}`);
434
+ * ```
435
+ */
244
436
  public log(data: string): void {
245
437
  const writer = new BytesWriter(String.UTF8.byteLength(data));
246
438
  writer.writeString(data);
@@ -249,6 +441,30 @@ export class BlockchainEnvironment {
249
441
  log(buffer.buffer, buffer.length);
250
442
  }
251
443
 
444
+ /**
445
+ * Emits a structured event for off-chain monitoring.
446
+ *
447
+ * @param event - NetEvent instance containing event data
448
+ *
449
+ * @remarks
450
+ * Events are the primary mechanism for dApps to track state changes.
451
+ * Events are not accessible within contracts.
452
+ *
453
+ * @example
454
+ * ```typescript
455
+ * class TransferredEvent extends NetEvent {
456
+ * constructor(operator: Address, from: Address, to: Address, amount: u256) {
457
+ * const data: BytesWriter = new BytesWriter(ADDRESS_BYTE_LENGTH * 3 + U256_BYTE_LENGTH);
458
+ * data.writeAddress(operator);
459
+ * data.writeAddress(from);
460
+ * data.writeAddress(to);
461
+ * data.writeU256(amount);
462
+ * super('Transferred', data);
463
+ * }
464
+ * }
465
+ * Blockchain.emit(new TransferredEvent(operator, sender, recipient, value));
466
+ * ```
467
+ */
252
468
  public emit(event: NetEvent): void {
253
469
  const data = event.getEventData();
254
470
  const writer = new BytesWriter(
@@ -261,15 +477,54 @@ export class BlockchainEnvironment {
261
477
  emit(writer.getBuffer().buffer, writer.bufferLength());
262
478
  }
263
479
 
480
+ /**
481
+ * Validates a Bitcoin address format for the current network.
482
+ *
483
+ * @param address - Bitcoin address string to validate
484
+ * @returns true if valid for current network, false otherwise
485
+ *
486
+ * @warning Validation rules are network-specific:
487
+ * - Mainnet: bc1, 1, 3 prefixes
488
+ * - Testnet: tb1, m, n, 2 prefixes
489
+ *
490
+ * @example
491
+ * ```typescript
492
+ * if (!Blockchain.validateBitcoinAddress(userAddress)) {
493
+ * throw new Revert("Invalid Bitcoin address");
494
+ * }
495
+ * ```
496
+ */
264
497
  public validateBitcoinAddress(address: string): bool {
265
498
  const writer = new BytesWriter(String.UTF8.byteLength(address));
266
499
  writer.writeString(address);
267
500
 
268
501
  const result = validateBitcoinAddress(writer.getBuffer().buffer, address.length);
269
-
270
502
  return result === 1;
271
503
  }
272
504
 
505
+ /**
506
+ * Deploys a new contract using an existing contract as template.
507
+ *
508
+ * @param existingAddress - Template contract address
509
+ * @param salt - Unique salt for deterministic addressing
510
+ * @param calldata - Constructor parameters
511
+ * @returns Address of newly deployed contract
512
+ * @throws {Revert} When deployment fails
513
+ *
514
+ * @warning CREATE2 style deployment:
515
+ * Same salt + template = same address.
516
+ * Salt collision will cause deployment to fail.
517
+ *
518
+ * @example
519
+ * ```typescript
520
+ * const salt = u256.fromBytes(sha256("unique-id"));
521
+ * const newToken = Blockchain.deployContractFromExisting(
522
+ * templateAddress,
523
+ * salt,
524
+ * constructorData
525
+ * );
526
+ * ```
527
+ */
273
528
  public deployContractFromExisting(
274
529
  existingAddress: Address,
275
530
  salt: u256,
@@ -286,7 +541,6 @@ export class BlockchainEnvironment {
286
541
  resultAddressBuffer,
287
542
  );
288
543
 
289
- // TODO: Decode revert data if status is not 0
290
544
  if (status !== 0) {
291
545
  throw new Revert('Failed to deploy contract');
292
546
  }
@@ -295,6 +549,21 @@ export class BlockchainEnvironment {
295
549
  return contractAddressReader.readAddress();
296
550
  }
297
551
 
552
+ /**
553
+ * Reads a value from persistent storage.
554
+ *
555
+ * @param pointerHash - 32-byte storage key
556
+ * @returns 32-byte stored value (zeros if unset)
557
+ *
558
+ * @warning Cannot distinguish between unset and explicitly set to zero.
559
+ * Use hasStorageAt() to check existence.
560
+ *
561
+ * @example
562
+ * ```typescript
563
+ * const key = sha256("balance:" + address);
564
+ * const balance = Blockchain.getStorageAt(key);
565
+ * ```
566
+ */
298
567
  public getStorageAt(pointerHash: Uint8Array): Uint8Array {
299
568
  this.hasPointerStorageHash(pointerHash);
300
569
 
@@ -305,6 +574,27 @@ export class BlockchainEnvironment {
305
574
  return new Uint8Array(32);
306
575
  }
307
576
 
577
+ /**
578
+ * Reads a value from transient storage (cleared after transaction).
579
+ *
580
+ * @param pointerHash - 32-byte storage key
581
+ * @returns 32-byte stored value (zeros if unset)
582
+ *
583
+ * @warning NOT CURRENTLY ENABLED IN PRODUCTION.
584
+ * Transient storage functionality is experimental and only available in testing.
585
+ * Will fail if called in production or testnet environments.
586
+ * Storage is cleared after each transaction when enabled.
587
+ *
588
+ * @example
589
+ * ```typescript
590
+ * // Reentrancy guard pattern (when enabled)
591
+ * const GUARD_KEY = sha256("reentrancy");
592
+ * if (Blockchain.hasTransientStorageAt(GUARD_KEY)) {
593
+ * throw new Revert("Reentrancy detected");
594
+ * }
595
+ * Blockchain.setTransientStorageAt(GUARD_KEY, u256.One.toBytes());
596
+ * ```
597
+ */
308
598
  public getTransientStorageAt(pointerHash: Uint8Array): Uint8Array {
309
599
  if (this.hasPointerTransientStorageHash(pointerHash)) {
310
600
  return this.transientStorage.get(pointerHash);
@@ -313,54 +603,206 @@ export class BlockchainEnvironment {
313
603
  return new Uint8Array(32);
314
604
  }
315
605
 
606
+ /**
607
+ * Computes SHA-256 hash of input data.
608
+ *
609
+ * @param buffer - Data to hash
610
+ * @returns 32-byte hash result
611
+ *
612
+ * @example
613
+ * ```typescript
614
+ * const hash = Blockchain.sha256(data);
615
+ * ```
616
+ */
617
+ @inline
316
618
  public sha256(buffer: Uint8Array): Uint8Array {
317
619
  return sha256(buffer);
318
620
  }
319
621
 
622
+ /**
623
+ * Computes double SHA-256 (Bitcoin's hash256).
624
+ *
625
+ * @param buffer - Data to hash
626
+ * @returns 32-byte double hash result
627
+ *
628
+ * @remarks
629
+ * Standard Bitcoin hash function used for transaction IDs and block hashes.
630
+ *
631
+ * @example
632
+ * ```typescript
633
+ * const txHash = Blockchain.hash256(transactionData);
634
+ * ```
635
+ */
636
+ @inline
320
637
  public hash256(buffer: Uint8Array): Uint8Array {
321
638
  return sha256(sha256(buffer));
322
639
  }
323
640
 
641
+ /**
642
+ * Verifies a Schnorr signature (Bitcoin Taproot).
643
+ *
644
+ * @param publicKey - 32-byte public key
645
+ * @param signature - 64-byte Schnorr signature
646
+ * @param hash - 32-byte message hash
647
+ * @returns true if signature is valid
648
+ *
649
+ * @warning Schnorr signatures differ from ECDSA:
650
+ * - Linear aggregation properties
651
+ * - Used in Taproot (post-2021)
652
+ *
653
+ * @example
654
+ * ```typescript
655
+ * const isValid = Blockchain.verifySchnorrSignature(
656
+ * signer,
657
+ * signature,
658
+ * messageHash
659
+ * );
660
+ * if (!isValid) throw new Revert("Invalid signature");
661
+ * ```
662
+ */
324
663
  public verifySchnorrSignature(
325
664
  publicKey: Address,
326
665
  signature: Uint8Array,
327
666
  hash: Uint8Array,
328
667
  ): boolean {
329
668
  const result: u32 = verifySchnorrSignature(publicKey.buffer, signature.buffer, hash.buffer);
330
-
331
669
  return result === 1;
332
670
  }
333
671
 
672
+ /**
673
+ * Checks if an address is a contract (not an EOA).
674
+ *
675
+ * @param address - Address to check
676
+ * @returns true if contract, false if EOA or uninitialized
677
+ *
678
+ * @warning Cannot distinguish between EOA with no transactions
679
+ * and uninitialized address.
680
+ *
681
+ * @example
682
+ * ```typescript
683
+ * if (!Blockchain.isContract(targetAddress)) {
684
+ * throw new Revert("Must be a contract");
685
+ * }
686
+ * ```
687
+ */
688
+ @inline
334
689
  public isContract(address: Address): boolean {
335
690
  return getAccountType(address.buffer) !== 0;
336
691
  }
337
692
 
693
+ /**
694
+ * Checks if persistent storage slot has a non-zero value.
695
+ *
696
+ * @param pointerHash - 32-byte storage key
697
+ * @returns true if slot contains non-zero value
698
+ *
699
+ * @warning Cannot distinguish between never written and explicitly set to zero.
700
+ *
701
+ * @example
702
+ * ```typescript
703
+ * const EXISTS_KEY = sha256("user:exists:" + address);
704
+ * if (!Blockchain.hasStorageAt(EXISTS_KEY)) {
705
+ * // First time user
706
+ * }
707
+ * ```
708
+ */
338
709
  public hasStorageAt(pointerHash: Uint8Array): bool {
339
- // We mark zero as the default value for the storage, if something is 0, the storage slot get deleted or is non-existent
340
710
  const val: Uint8Array = this.getStorageAt(pointerHash);
341
-
342
711
  return !eqUint(val, EMPTY_BUFFER);
343
712
  }
344
713
 
714
+ /**
715
+ * Checks if transient storage slot has a non-zero value.
716
+ *
717
+ * @param pointerHash - 32-byte storage key
718
+ * @returns true if slot contains non-zero value
719
+ *
720
+ * @warning NOT CURRENTLY ENABLED IN PRODUCTION.
721
+ * Transient storage functionality is experimental and only available in testing.
722
+ * Will fail if called in production or testnet environments.
723
+ *
724
+ * @remarks
725
+ * Only reliable within single transaction context when enabled.
726
+ */
345
727
  public hasTransientStorageAt(pointerHash: Uint8Array): bool {
346
- // We mark zero as the default value for the storage, if something is 0, the storage slot get deleted or is non-existent
347
728
  const val: Uint8Array = this.getTransientStorageAt(pointerHash);
348
-
349
729
  return !eqUint(val, EMPTY_BUFFER);
350
730
  }
351
731
 
732
+ /**
733
+ * Writes a value to persistent storage.
734
+ *
735
+ * @param pointerHash - 32-byte storage key
736
+ * @param value - Value to store (will be padded/truncated to 32 bytes)
737
+ *
738
+ * @warning Storage writes cost significant gas (~20,000).
739
+ * Batch related updates when possible.
740
+ *
741
+ * @example
742
+ * ```typescript
743
+ * const key = sha256("balance:" + address);
744
+ * Blockchain.setStorageAt(key, balance.toBytes());
745
+ * ```
746
+ */
747
+ @inline
352
748
  public setStorageAt(pointerHash: Uint8Array, value: Uint8Array): void {
353
749
  this._internalSetStorageAt(pointerHash, value);
354
750
  }
355
751
 
752
+ /**
753
+ * Writes a value to transient storage.
754
+ *
755
+ * @param pointerHash - 32-byte storage key
756
+ * @param value - 32-byte value to store
757
+ * @throws {Revert} If key or value is not exactly 32 bytes
758
+ *
759
+ * @warning NOT CURRENTLY ENABLED IN PRODUCTION.
760
+ * Transient storage functionality is experimental and only available in testing.
761
+ * Will fail if called in production or testnet environments.
762
+ * Value must be exactly 32 bytes (no auto-padding).
763
+ * Storage is cleared after transaction completes when enabled.
764
+ *
765
+ * @example
766
+ * ```typescript
767
+ * // Reentrancy lock (when enabled)
768
+ * Blockchain.setTransientStorageAt(LOCK_KEY, u256.One.toBytes());
769
+ * // Lock automatically clears after transaction
770
+ * ```
771
+ */
772
+ @inline
356
773
  public setTransientStorageAt(pointerHash: Uint8Array, value: Uint8Array): void {
357
774
  this._internalSetTransientStorageAt(pointerHash, value);
358
775
  }
359
776
 
777
+ /**
778
+ * Gets the account type identifier for an address.
779
+ *
780
+ * @param address - Address to query
781
+ * @returns Account type code (0 = EOA/uninitialized, >0 = contract type)
782
+ *
783
+ * @remarks
784
+ * Different contract types may have different codes.
785
+ */
360
786
  public getAccountType(address: Address): u32 {
361
787
  return getAccountType(address.buffer);
362
788
  }
363
789
 
790
+ /**
791
+ * Retrieves a historical block hash.
792
+ *
793
+ * @param blockNumber - Block number to query
794
+ * @returns 32-byte block hash
795
+ *
796
+ * @warning Only recent blocks available (~256 blocks).
797
+ * Older blocks return zero hash.
798
+ * Do not use for randomness generation.
799
+ *
800
+ * @example
801
+ * ```typescript
802
+ * const oldBlock = Blockchain.block.number - 10;
803
+ * const hash = Blockchain.getBlockHash(oldBlock);
804
+ * ```
805
+ */
364
806
  public getBlockHash(blockNumber: u64): Uint8Array {
365
807
  const hash = new ArrayBuffer(32);
366
808
  getBlockHash(blockNumber, hash);
@@ -378,13 +820,21 @@ export class BlockchainEnvironment {
378
820
  }
379
821
 
380
822
  private _internalSetStorageAt(pointerHash: Uint8Array, value: Uint8Array): void {
381
- this.storage.set(pointerHash, value);
823
+ if (pointerHash.buffer.byteLength !== 32) {
824
+ throw new Revert('Pointer must be 32 bytes long');
825
+ }
382
826
 
383
- if (pointerHash.buffer.byteLength !== 32 || value.buffer.byteLength !== 32) {
384
- throw new Revert('Pointer and value must be 32 bytes long');
827
+ let finalValue: Uint8Array = value;
828
+ if (value.buffer.byteLength !== 32) {
829
+ // Auto-pad values shorter than 32 bytes
830
+ finalValue = new Uint8Array(32);
831
+ for (let i = 0; i < value.buffer.byteLength && i < 32; i++) {
832
+ finalValue[i] = value[i];
833
+ }
385
834
  }
386
835
 
387
- storePointer(pointerHash.buffer, value.buffer);
836
+ this.storage.set(pointerHash, finalValue);
837
+ storePointer(pointerHash.buffer, finalValue.buffer);
388
838
  }
389
839
 
390
840
  private _internalSetTransientStorageAt(pointerHash: Uint8Array, value: Uint8Array): void {
@@ -398,39 +848,37 @@ export class BlockchainEnvironment {
398
848
  }
399
849
 
400
850
  private hasPointerStorageHash(pointer: Uint8Array): bool {
401
- if (this.storage.has(pointer)) {
402
- return true;
403
- }
404
-
405
851
  if (pointer.buffer.byteLength !== 32) {
406
852
  throw new Revert('Pointer must be 32 bytes long');
407
853
  }
408
854
 
409
- // we attempt to load the requested pointer.
855
+ if (this.storage.has(pointer)) {
856
+ return true;
857
+ }
858
+
410
859
  const resultBuffer = new ArrayBuffer(32);
411
860
  loadPointer(pointer.buffer, resultBuffer);
412
861
 
413
862
  const value: Uint8Array = Uint8Array.wrap(resultBuffer);
414
- this.storage.set(pointer, value); // cache the value
863
+ this.storage.set(pointer, value); // Cache for future reads
415
864
 
416
865
  return !eqUint(value, EMPTY_BUFFER);
417
866
  }
418
867
 
419
868
  private hasPointerTransientStorageHash(pointer: Uint8Array): bool {
420
- if (this.transientStorage.has(pointer)) {
421
- return true;
422
- }
423
-
424
869
  if (pointer.buffer.byteLength !== 32) {
425
870
  throw new Revert('Transient pointer must be 32 bytes long');
426
871
  }
427
872
 
428
- // we attempt to load the requested pointer.
873
+ if (this.transientStorage.has(pointer)) {
874
+ return true;
875
+ }
876
+
429
877
  const resultBuffer = new ArrayBuffer(32);
430
878
  tLoadPointer(pointer.buffer, resultBuffer);
431
879
 
432
880
  const value: Uint8Array = Uint8Array.wrap(resultBuffer);
433
- this.transientStorage.set(pointer, value); // cache the value
881
+ this.transientStorage.set(pointer, value); // Cache for future reads
434
882
 
435
883
  return !eqUint(value, EMPTY_BUFFER);
436
884
  }