@btc-vision/btc-runtime 1.10.10 → 1.10.12

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 (45) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +258 -137
  3. package/SECURITY.md +226 -0
  4. package/docs/README.md +614 -0
  5. package/docs/advanced/bitcoin-scripts.md +939 -0
  6. package/docs/advanced/cross-contract-calls.md +579 -0
  7. package/docs/advanced/plugins.md +1006 -0
  8. package/docs/advanced/quantum-resistance.md +660 -0
  9. package/docs/advanced/signature-verification.md +715 -0
  10. package/docs/api-reference/blockchain.md +729 -0
  11. package/docs/api-reference/events.md +642 -0
  12. package/docs/api-reference/op20.md +902 -0
  13. package/docs/api-reference/op721.md +819 -0
  14. package/docs/api-reference/safe-math.md +510 -0
  15. package/docs/api-reference/storage.md +731 -0
  16. package/docs/contracts/op-net-base.md +786 -0
  17. package/docs/contracts/op20-token.md +687 -0
  18. package/docs/contracts/op20s-signatures.md +614 -0
  19. package/docs/contracts/op721-nft.md +785 -0
  20. package/docs/contracts/reentrancy-guard.md +787 -0
  21. package/docs/core-concepts/blockchain-environment.md +724 -0
  22. package/docs/core-concepts/decorators.md +466 -0
  23. package/docs/core-concepts/events.md +652 -0
  24. package/docs/core-concepts/pointers.md +391 -0
  25. package/docs/core-concepts/security.md +370 -0
  26. package/docs/core-concepts/storage-system.md +938 -0
  27. package/docs/examples/basic-token.md +745 -0
  28. package/docs/examples/nft-with-reservations.md +1210 -0
  29. package/docs/examples/oracle-integration.md +1212 -0
  30. package/docs/examples/stablecoin.md +1180 -0
  31. package/docs/getting-started/first-contract.md +575 -0
  32. package/docs/getting-started/installation.md +384 -0
  33. package/docs/getting-started/project-structure.md +630 -0
  34. package/docs/storage/memory-maps.md +721 -0
  35. package/docs/storage/stored-arrays.md +714 -0
  36. package/docs/storage/stored-maps.md +686 -0
  37. package/docs/storage/stored-primitives.md +608 -0
  38. package/docs/types/address.md +773 -0
  39. package/docs/types/bytes-writer-reader.md +938 -0
  40. package/docs/types/calldata.md +744 -0
  41. package/docs/types/safe-math.md +403 -0
  42. package/package.json +51 -26
  43. package/runtime/memory/MapOfMap.ts +1 -0
  44. package/runtime/types/SafeMath.ts +121 -1
  45. package/LICENSE.md +0 -21
@@ -0,0 +1,608 @@
1
+ # Stored Primitives
2
+
3
+ Stored primitives are typed wrappers for single values that persist on-chain. They handle storage reading, writing, and caching automatically.
4
+
5
+ ## Overview
6
+
7
+ ```typescript
8
+ import {
9
+ StoredU256,
10
+ StoredU64,
11
+ StoredU32,
12
+ StoredBoolean,
13
+ StoredString,
14
+ StoredAddress,
15
+ Blockchain,
16
+ EMPTY_POINTER,
17
+ } from '@btc-vision/btc-runtime/runtime';
18
+ import { u256 } from '@btc-vision/as-bignum/assembly';
19
+
20
+ // Allocate storage pointer
21
+ private counterPointer: u16 = Blockchain.nextPointer;
22
+
23
+ // Create stored value with default
24
+ private counter: StoredU256 = new StoredU256(this.counterPointer, EMPTY_POINTER);
25
+
26
+ // Read and write
27
+ const current = this.counter.value; // Read
28
+ this.counter.value = newValue; // Write
29
+ ```
30
+
31
+ ## Class Hierarchy
32
+
33
+ The stored primitives are standalone final classes (not inheriting from a common base):
34
+
35
+ ```mermaid
36
+ classDiagram
37
+ class StoredU256 {
38
+ -pointer: u16
39
+ -subPointer: Uint8Array
40
+ -_value: u256
41
+ +value: u256 getter/setter
42
+ +add(value: u256) this
43
+ +sub(value: u256) this
44
+ +mul(value: u256) this
45
+ +set(value: u256) this
46
+ +toString() string
47
+ }
48
+
49
+ class StoredU64 {
50
+ -pointer: u16
51
+ -subPointer: Uint8Array
52
+ -_values: u64[4]
53
+ +get(index: u8) u64
54
+ +set(index: u8, value: u64) void
55
+ +save() void
56
+ +getAll() u64[]
57
+ }
58
+
59
+ class StoredU32 {
60
+ -pointer: u16
61
+ -subPointer: Uint8Array
62
+ -_values: u32[8]
63
+ +get(index: u8) u32
64
+ +set(index: u8, value: u32) void
65
+ +save() void
66
+ +getAll() u32[]
67
+ }
68
+
69
+ class StoredBoolean {
70
+ -pointer: u16
71
+ -_value: Uint8Array
72
+ +value: bool getter/setter
73
+ +toUint8Array() Uint8Array
74
+ }
75
+
76
+ class StoredString {
77
+ -pointer: u16
78
+ -index: u64
79
+ +value: string getter/setter
80
+ }
81
+
82
+ class StoredAddress {
83
+ -pointer: u16
84
+ -_value: Address
85
+ +value: Address getter/setter
86
+ +isDead() bool
87
+ }
88
+ ```
89
+
90
+ ## Available Types
91
+
92
+ | Type | Value Type | Size | Description |
93
+ |------|------------|------|-------------|
94
+ | `StoredU256` | `u256` | 32 bytes | 256-bit unsigned integer |
95
+ | `StoredU64` | `u64[4]` | 32 bytes | Stores 4 u64 values in one slot |
96
+ | `StoredU32` | `u32[8]` | 32 bytes | Stores 8 u32 values in one slot |
97
+ | `StoredBoolean` | `bool` | 32 bytes | Boolean value |
98
+ | `StoredString` | `string` | Variable | UTF-8 string (max 65,535 bytes) |
99
+ | `StoredAddress` | `Address` | 32 bytes | Address value |
100
+
101
+ > **Note:** `StoredU64` and `StoredU32` are packed storage types that store multiple values in a single 256-bit storage slot. Use `get(index)` and `set(index, value)` to access individual values, then call `save()` to persist changes.
102
+
103
+ ## Storage Key Generation
104
+
105
+ Each stored primitive computes its storage key using `SHA256(pointer || subPointer)`. See [Pointers](../core-concepts/pointers.md#encodepointer-function-flow) for the detailed flow diagram.
106
+
107
+ ## Usage
108
+
109
+ ### StoredU256
110
+
111
+ ```typescript
112
+ // Declaration
113
+ private balancePointer: u16 = Blockchain.nextPointer;
114
+ private _balance: StoredU256 = new StoredU256(this.balancePointer, EMPTY_POINTER);
115
+
116
+ // Read
117
+ const balance: u256 = this._balance.value;
118
+
119
+ // Write
120
+ this._balance.value = newBalance;
121
+
122
+ // Arithmetic
123
+ this._balance.value = SafeMath.add(this._balance.value, amount);
124
+ ```
125
+
126
+ ### StoredBoolean
127
+
128
+ ```typescript
129
+ // Declaration
130
+ private pausedPointer: u16 = Blockchain.nextPointer;
131
+ private _paused: StoredBoolean = new StoredBoolean(this.pausedPointer, false);
132
+
133
+ // Read
134
+ if (this._paused.value) {
135
+ throw new Revert('Contract is paused');
136
+ }
137
+
138
+ // Write
139
+ this._paused.value = true;
140
+ ```
141
+
142
+ ### StoredString
143
+
144
+ ```typescript
145
+ // Declaration
146
+ private namePointer: u16 = Blockchain.nextPointer;
147
+ private _name: StoredString = new StoredString(this.namePointer, 0);
148
+
149
+ // Write (typically in onDeployment)
150
+ this._name.value = 'My Token';
151
+
152
+ // Read
153
+ const name: string = this._name.value;
154
+ ```
155
+
156
+ ### StoredAddress
157
+
158
+ ```typescript
159
+ // Declaration - takes only pointer (default value is Address.zero())
160
+ private ownerPointer: u16 = Blockchain.nextPointer;
161
+ private _owner: StoredAddress = new StoredAddress(this.ownerPointer);
162
+
163
+ // Write
164
+ this._owner.value = Blockchain.tx.origin;
165
+
166
+ // Read
167
+ const owner: Address = this._owner.value;
168
+
169
+ // Check if address is zero (Note: isDead() in StoredAddress actually checks for zero address)
170
+ if (this._owner.isDead()) {
171
+ throw new Revert('Owner not set');
172
+ }
173
+
174
+ // Alternative: use isZero() on the Address instance directly
175
+ if (this._owner.value.isZero()) {
176
+ throw new Revert('Owner not set');
177
+ }
178
+
179
+ // Compare
180
+ if (!Blockchain.tx.sender.equals(this._owner.value)) {
181
+ throw new Revert('Not owner');
182
+ }
183
+ ```
184
+
185
+ ## Storage Behavior
186
+
187
+ ### Lazy Loading (Value Read Flow)
188
+
189
+ Values are loaded from storage on first access. The read flow follows this pattern:
190
+
191
+ ```mermaid
192
+ ---
193
+ config:
194
+ theme: dark
195
+ ---
196
+ flowchart LR
197
+ A["Access .value"] --> B{"Cached?"}
198
+ B -->|"Yes"| C["Return cached"]
199
+ B -->|"No"| D["ensureValue()"]
200
+ D --> E["encodePointer()"]
201
+ E --> F["getStorageAt()"]
202
+ F --> G["decode()"]
203
+ G --> H["Cache & Return"]
204
+ ```
205
+
206
+ ```typescript
207
+ // First access triggers storage read
208
+ const balance = this._balance.value; // Reads from storage
209
+
210
+ // Subsequent accesses use cached value
211
+ const balance2 = this._balance.value; // Uses cache (no storage read)
212
+ ```
213
+
214
+ ### Automatic Commit (Value Write Flow)
215
+
216
+ Changes are committed to storage automatically following this flow:
217
+
218
+ ```mermaid
219
+ ---
220
+ config:
221
+ theme: dark
222
+ ---
223
+ flowchart LR
224
+ A["Set .value"] --> B["encode()"]
225
+ B --> C["Update cache"]
226
+ C --> D["encodePointer()"]
227
+ D --> E["setStorageAt()"]
228
+ E --> F["Committed"]
229
+ ```
230
+
231
+ ```typescript
232
+ // Write value
233
+ this._balance.value = newBalance; // Marks as dirty
234
+
235
+ // Value is committed at transaction end
236
+ // (or immediately in some implementations)
237
+ ```
238
+
239
+ ### Manual Commit Control
240
+
241
+ For advanced use cases:
242
+
243
+ ```typescript
244
+ // Some stored types support NoCommit for read-only access
245
+ const value = this._balance.valueNoCommit; // Read without triggering commit
246
+
247
+ // Useful for view functions that shouldn't modify storage
248
+ ```
249
+
250
+ ## Initialization
251
+
252
+ ### Default Values
253
+
254
+ Always provide a meaningful default:
255
+
256
+ ```typescript
257
+ // Good: Zero/empty defaults
258
+ private counter: StoredU256 = new StoredU256(ptr, EMPTY_POINTER);
259
+ private name: StoredString = new StoredString(ptr, 0);
260
+ private paused: StoredBoolean = new StoredBoolean(ptr, false);
261
+ private owner: StoredAddress = new StoredAddress(ptr); // Default is Address.zero()
262
+
263
+ // The default is returned when storage slot is empty (never written)
264
+ ```
265
+
266
+ ### Setting Initial Values
267
+
268
+ Set values in `onDeployment`:
269
+
270
+ ```typescript
271
+ public override onDeployment(calldata: Calldata): void {
272
+ // Set initial values
273
+ this._name.value = calldata.readString();
274
+ this._symbol.value = calldata.readString();
275
+ this._totalSupply.value = calldata.readU256();
276
+ this._owner.value = Blockchain.tx.origin;
277
+ }
278
+ ```
279
+
280
+ ## Solidity vs OPNet Comparison
281
+
282
+ ### Quick Reference Table
283
+
284
+ | Solidity | OPNet | Default Value |
285
+ |----------|-------|---------------|
286
+ | `uint256 public value;` | `StoredU256` | `u256.Zero` |
287
+ | `uint64[4] packed;` | `StoredU64` | `[0, 0, 0, 0]` |
288
+ | `uint32[8] packed;` | `StoredU32` | `[0, 0, 0, 0, 0, 0, 0, 0]` |
289
+ | `string public name;` | `StoredString` | `""` |
290
+ | `bool public paused;` | `StoredBoolean` | `false` |
291
+ | `address public owner;` | `StoredAddress` | `Address.zero()` |
292
+
293
+ > **Note:** `StoredU64` and `StoredU32` pack multiple values into a single storage slot for efficiency. For single-value storage, use `StoredU256` with appropriate conversions.
294
+
295
+ ### Operations Comparison
296
+
297
+ | Operation | Solidity | OPNet |
298
+ |-----------|----------|-------|
299
+ | Declare state variable | `uint256 public value;` | `private _value: StoredU256 = new StoredU256(ptr, EMPTY_POINTER);` |
300
+ | Read value | `value` or `this.value` | `this._value.value` |
301
+ | Write value | `value = newValue;` | `this._value.value = newValue;` |
302
+ | Increment | `value++;` | `this._value.value = SafeMath.add(this._value.value, u256.One);` |
303
+ | Decrement | `value--;` | `this._value.value = SafeMath.sub(this._value.value, u256.One);` |
304
+ | Add amount | `value += amount;` | `this._value.value = SafeMath.add(this._value.value, amount);` |
305
+ | Check zero | `value == 0` | `this._value.value.isZero()` |
306
+ | Compare | `value > other` | `this._value.value > other` |
307
+ | Set in constructor | `value = initial;` | Use `onDeployment()` |
308
+ | Public getter | Automatic | Must define manually |
309
+
310
+ ### Declaration Patterns
311
+
312
+ | Solidity Pattern | OPNet Equivalent |
313
+ |------------------|------------------|
314
+ | `uint256 public totalSupply;` | `private totalSupplyPtr: u16 = Blockchain.nextPointer;`<br>`private _totalSupply: StoredU256 = new StoredU256(this.totalSupplyPtr, EMPTY_POINTER);` |
315
+ | `string public name = "Token";` | `private namePtr: u16 = Blockchain.nextPointer;`<br>`private _name: StoredString = new StoredString(this.namePtr, 0);`<br>Then in `onDeployment`: `this._name.value = "Token";` |
316
+ | `bool public paused = false;` | `private pausedPtr: u16 = Blockchain.nextPointer;`<br>`private _paused: StoredBoolean = new StoredBoolean(this.pausedPtr, false);` |
317
+ | `address public owner;` | `private ownerPtr: u16 = Blockchain.nextPointer;`<br>`private _owner: StoredAddress = new StoredAddress(this.ownerPtr);` |
318
+
319
+ For complete token examples using stored primitives, see [Basic Token Example](../examples/basic-token.md).
320
+
321
+ ## Side-by-Side Code Examples
322
+
323
+ ### Counter Contract
324
+
325
+ **Solidity:**
326
+ ```solidity
327
+ contract Counter {
328
+ uint256 public count;
329
+
330
+ function increment() external {
331
+ count++;
332
+ }
333
+
334
+ function decrement() external {
335
+ require(count > 0, "Cannot go below zero");
336
+ count--;
337
+ }
338
+
339
+ function add(uint256 amount) external {
340
+ count += amount;
341
+ }
342
+
343
+ function reset() external {
344
+ count = 0;
345
+ }
346
+ }
347
+ ```
348
+
349
+ **OPNet:**
350
+ ```typescript
351
+ @final
352
+ export class Counter extends OP_NET {
353
+ private countPointer: u16 = Blockchain.nextPointer;
354
+ private _count: StoredU256 = new StoredU256(this.countPointer, EMPTY_POINTER);
355
+
356
+ public increment(_calldata: Calldata): BytesWriter {
357
+ this._count.value = SafeMath.add(this._count.value, u256.One);
358
+ return new BytesWriter(0);
359
+ }
360
+
361
+ public decrement(_calldata: Calldata): BytesWriter {
362
+ if (this._count.value.isZero()) {
363
+ throw new Revert('Cannot go below zero');
364
+ }
365
+ this._count.value = SafeMath.sub(this._count.value, u256.One);
366
+ return new BytesWriter(0);
367
+ }
368
+
369
+ public add(calldata: Calldata): BytesWriter {
370
+ const amount = calldata.readU256();
371
+ this._count.value = SafeMath.add(this._count.value, amount);
372
+ return new BytesWriter(0);
373
+ }
374
+
375
+ public reset(_calldata: Calldata): BytesWriter {
376
+ this._count.value = u256.Zero;
377
+ return new BytesWriter(0);
378
+ }
379
+
380
+ public count(_calldata: Calldata): BytesWriter {
381
+ const writer = new BytesWriter(32);
382
+ writer.writeU256(this._count.value);
383
+ return writer;
384
+ }
385
+ }
386
+ ```
387
+
388
+ ### Ownable Contract
389
+
390
+ **Solidity:**
391
+ ```solidity
392
+ contract Ownable {
393
+ address public owner;
394
+ bool public paused;
395
+
396
+ modifier onlyOwner() {
397
+ require(msg.sender == owner, "Not owner");
398
+ _;
399
+ }
400
+
401
+ constructor() {
402
+ owner = msg.sender;
403
+ }
404
+
405
+ function transferOwnership(address newOwner) external onlyOwner {
406
+ require(newOwner != address(0), "Invalid address");
407
+ owner = newOwner;
408
+ }
409
+
410
+ function pause() external onlyOwner {
411
+ paused = true;
412
+ }
413
+
414
+ function unpause() external onlyOwner {
415
+ paused = false;
416
+ }
417
+ }
418
+ ```
419
+
420
+ **OPNet:**
421
+ ```typescript
422
+ @final
423
+ export class Ownable extends OP_NET {
424
+ private ownerPointer: u16 = Blockchain.nextPointer;
425
+ private pausedPointer: u16 = Blockchain.nextPointer;
426
+
427
+ private _owner: StoredAddress = new StoredAddress(this.ownerPointer);
428
+ private _paused: StoredBoolean = new StoredBoolean(this.pausedPointer, false);
429
+
430
+ public override onDeployment(_calldata: Calldata): void {
431
+ this._owner.value = Blockchain.tx.origin;
432
+ }
433
+
434
+ private onlyOwner(): void {
435
+ if (!Blockchain.tx.sender.equals(this._owner.value)) {
436
+ throw new Revert('Not owner');
437
+ }
438
+ }
439
+
440
+ public transferOwnership(calldata: Calldata): BytesWriter {
441
+ this.onlyOwner();
442
+ const newOwner = calldata.readAddress();
443
+ if (newOwner.equals(Address.zero())) {
444
+ throw new Revert('Invalid address');
445
+ }
446
+ this._owner.value = newOwner;
447
+ return new BytesWriter(0);
448
+ }
449
+
450
+ public pause(_calldata: Calldata): BytesWriter {
451
+ this.onlyOwner();
452
+ this._paused.value = true;
453
+ return new BytesWriter(0);
454
+ }
455
+
456
+ public unpause(_calldata: Calldata): BytesWriter {
457
+ this.onlyOwner();
458
+ this._paused.value = false;
459
+ return new BytesWriter(0);
460
+ }
461
+
462
+ public owner(_calldata: Calldata): BytesWriter {
463
+ const writer = new BytesWriter(32);
464
+ writer.writeAddress(this._owner.value);
465
+ return writer;
466
+ }
467
+
468
+ public paused(_calldata: Calldata): BytesWriter {
469
+ const writer = new BytesWriter(1);
470
+ writer.writeBoolean(this._paused.value);
471
+ return writer;
472
+ }
473
+ }
474
+ ```
475
+
476
+ ## Patterns
477
+
478
+ ### Read-Modify-Write
479
+
480
+ ```typescript
481
+ // Increment counter
482
+ public increment(_calldata: Calldata): BytesWriter {
483
+ const current = this._counter.value;
484
+ this._counter.value = SafeMath.add(current, u256.One);
485
+ return new BytesWriter(0);
486
+ }
487
+
488
+ // Toggle boolean
489
+ public togglePause(_calldata: Calldata): BytesWriter {
490
+ this.onlyDeployer(Blockchain.tx.sender);
491
+ this._paused.value = !this._paused.value;
492
+ return new BytesWriter(0);
493
+ }
494
+ ```
495
+
496
+ ### Conditional Updates
497
+
498
+ ```typescript
499
+ public setOwner(calldata: Calldata): BytesWriter {
500
+ this.onlyDeployer(Blockchain.tx.sender);
501
+
502
+ const newOwner = calldata.readAddress();
503
+
504
+ // Validate before writing
505
+ if (newOwner.equals(Address.zero())) {
506
+ throw new Revert('Invalid owner');
507
+ }
508
+
509
+ // Only write if different
510
+ if (!newOwner.equals(this._owner.value)) {
511
+ this._owner.value = newOwner;
512
+ this.emitEvent(new OwnershipTransferred(this._owner.value, newOwner));
513
+ }
514
+
515
+ return new BytesWriter(0);
516
+ }
517
+ ```
518
+
519
+ ### View Functions
520
+
521
+ ```typescript
522
+ // Return stored value
523
+ public totalSupply(_calldata: Calldata): BytesWriter {
524
+ const writer = new BytesWriter(32);
525
+ writer.writeU256(this._totalSupply.value);
526
+ return writer;
527
+ }
528
+
529
+ // Return multiple values
530
+ public getInfo(_calldata: Calldata): BytesWriter {
531
+ const writer = new BytesWriter(256);
532
+ writer.writeString(this._name.value);
533
+ writer.writeString(this._symbol.value);
534
+ writer.writeU256(this._totalSupply.value);
535
+ writer.writeU8(this._decimals.value);
536
+ return writer;
537
+ }
538
+ ```
539
+
540
+ ## Best Practices
541
+
542
+ ### 1. Initialize All Storage
543
+
544
+ ```typescript
545
+ // Always set initial values in onDeployment
546
+ public override onDeployment(calldata: Calldata): void {
547
+ this._name.value = 'Token';
548
+ this._symbol.value = 'TKN';
549
+ this._decimals.value = 18;
550
+ this._owner.value = Blockchain.tx.origin;
551
+ }
552
+ ```
553
+
554
+ ### 2. Use Meaningful Defaults
555
+
556
+ ```typescript
557
+ // Good: EMPTY_POINTER for uninitialized u256 values
558
+ private counter: StoredU256 = new StoredU256(ptr, EMPTY_POINTER);
559
+
560
+ // Note: Set initial values in onDeployment if needed
561
+ // this._counter.value = u256.fromU64(100);
562
+ ```
563
+
564
+ ### 3. Validate Before Writing
565
+
566
+ ```typescript
567
+ public setLimit(calldata: Calldata): BytesWriter {
568
+ const newLimit = calldata.readU256();
569
+
570
+ // Validate
571
+ if (newLimit.isZero()) {
572
+ throw new Revert('Limit cannot be zero');
573
+ }
574
+
575
+ if (newLimit > u256.fromU64(1000000)) {
576
+ throw new Revert('Limit too high');
577
+ }
578
+
579
+ // Then write
580
+ this._limit.value = newLimit;
581
+ return new BytesWriter(0);
582
+ }
583
+ ```
584
+
585
+ ### 4. Cache Reads in Loops
586
+
587
+ ```typescript
588
+ // Bad: Multiple storage reads
589
+ for (let i = 0; i < count; i++) {
590
+ if (amount > this._balance.value) { // Storage read each iteration
591
+ // ...
592
+ }
593
+ }
594
+
595
+ // Good: Cache the value
596
+ const balance = this._balance.value; // One storage read
597
+ for (let i = 0; i < count; i++) {
598
+ if (amount > balance) {
599
+ // ...
600
+ }
601
+ }
602
+ ```
603
+
604
+ ---
605
+
606
+ **Navigation:**
607
+ - Previous: [BytesWriter/Reader](../types/bytes-writer-reader.md)
608
+ - Next: [Stored Arrays](./stored-arrays.md)