@btc-vision/btc-runtime 1.10.8 → 1.10.11

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 (44) 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 +840 -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 +473 -0
  26. package/docs/core-concepts/storage-system.md +969 -0
  27. package/docs/examples/basic-token.md +745 -0
  28. package/docs/examples/nft-with-reservations.md +1440 -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 +764 -0
  35. package/docs/storage/stored-arrays.md +778 -0
  36. package/docs/storage/stored-maps.md +758 -0
  37. package/docs/storage/stored-primitives.md +655 -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 +446 -0
  42. package/package.json +52 -27
  43. package/runtime/memory/MapOfMap.ts +1 -0
  44. package/LICENSE.md +0 -21
@@ -0,0 +1,655 @@
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 by combining the pointer and subPointer:
106
+
107
+ ```mermaid
108
+ flowchart LR
109
+ A["pointer: u16<br/>subPointer: u256"] --> B["32-byte buffer<br/>[0-1] = pointer<br/>[2-31] = subPointer"]
110
+ B --> C["SHA256"]
111
+ C --> D["Storage Key<br/>(32 bytes)"]
112
+ ```
113
+
114
+ ## Usage
115
+
116
+ ### StoredU256
117
+
118
+ ```typescript
119
+ // Declaration
120
+ private balancePointer: u16 = Blockchain.nextPointer;
121
+ private _balance: StoredU256 = new StoredU256(this.balancePointer, EMPTY_POINTER);
122
+
123
+ // Read
124
+ const balance: u256 = this._balance.value;
125
+
126
+ // Write
127
+ this._balance.value = newBalance;
128
+
129
+ // Arithmetic
130
+ this._balance.value = SafeMath.add(this._balance.value, amount);
131
+ ```
132
+
133
+ ### StoredBoolean
134
+
135
+ ```typescript
136
+ // Declaration
137
+ private pausedPointer: u16 = Blockchain.nextPointer;
138
+ private _paused: StoredBoolean = new StoredBoolean(this.pausedPointer, false);
139
+
140
+ // Read
141
+ if (this._paused.value) {
142
+ throw new Revert('Contract is paused');
143
+ }
144
+
145
+ // Write
146
+ this._paused.value = true;
147
+ ```
148
+
149
+ ### StoredString
150
+
151
+ ```typescript
152
+ // Declaration
153
+ private namePointer: u16 = Blockchain.nextPointer;
154
+ private _name: StoredString = new StoredString(this.namePointer, 0);
155
+
156
+ // Write (typically in onDeployment)
157
+ this._name.value = 'My Token';
158
+
159
+ // Read
160
+ const name: string = this._name.value;
161
+ ```
162
+
163
+ ### StoredAddress
164
+
165
+ ```typescript
166
+ // Declaration - takes only pointer (default value is Address.zero())
167
+ private ownerPointer: u16 = Blockchain.nextPointer;
168
+ private _owner: StoredAddress = new StoredAddress(this.ownerPointer);
169
+
170
+ // Write
171
+ this._owner.value = Blockchain.tx.origin;
172
+
173
+ // Read
174
+ const owner: Address = this._owner.value;
175
+
176
+ // Check if address is zero (Note: isDead() in StoredAddress actually checks for zero address)
177
+ if (this._owner.isDead()) {
178
+ throw new Revert('Owner not set');
179
+ }
180
+
181
+ // Alternative: use isZero() on the Address instance directly
182
+ if (this._owner.value.isZero()) {
183
+ throw new Revert('Owner not set');
184
+ }
185
+
186
+ // Compare
187
+ if (!Blockchain.tx.sender.equals(this._owner.value)) {
188
+ throw new Revert('Not owner');
189
+ }
190
+ ```
191
+
192
+ ## Storage Behavior
193
+
194
+ ### Lazy Loading (Value Read Flow)
195
+
196
+ Values are loaded from storage on first access. The read flow follows this pattern:
197
+
198
+ ```mermaid
199
+ ---
200
+ config:
201
+ theme: dark
202
+ ---
203
+ flowchart LR
204
+ A["Access .value"] --> B{"Cached?"}
205
+ B -->|"Yes"| C["Return cached"]
206
+ B -->|"No"| D["ensureValue()"]
207
+ D --> E["encodePointer()"]
208
+ E --> F["getStorageAt()"]
209
+ F --> G["decode()"]
210
+ G --> H["Cache & Return"]
211
+ ```
212
+
213
+ ```typescript
214
+ // First access triggers storage read
215
+ const balance = this._balance.value; // Reads from storage
216
+
217
+ // Subsequent accesses use cached value
218
+ const balance2 = this._balance.value; // Uses cache (no storage read)
219
+ ```
220
+
221
+ ### Automatic Commit (Value Write Flow)
222
+
223
+ Changes are committed to storage automatically following this flow:
224
+
225
+ ```mermaid
226
+ ---
227
+ config:
228
+ theme: dark
229
+ ---
230
+ flowchart LR
231
+ A["Set .value"] --> B["encode()"]
232
+ B --> C["Update cache"]
233
+ C --> D["encodePointer()"]
234
+ D --> E["setStorageAt()"]
235
+ E --> F["Committed"]
236
+ ```
237
+
238
+ ```typescript
239
+ // Write value
240
+ this._balance.value = newBalance; // Marks as dirty
241
+
242
+ // Value is committed at transaction end
243
+ // (or immediately in some implementations)
244
+ ```
245
+
246
+ ### Manual Commit Control
247
+
248
+ For advanced use cases:
249
+
250
+ ```typescript
251
+ // Some stored types support NoCommit for read-only access
252
+ const value = this._balance.valueNoCommit; // Read without triggering commit
253
+
254
+ // Useful for view functions that shouldn't modify storage
255
+ ```
256
+
257
+ ## Initialization
258
+
259
+ ### Default Values
260
+
261
+ Always provide a meaningful default:
262
+
263
+ ```typescript
264
+ // Good: Zero/empty defaults
265
+ private counter: StoredU256 = new StoredU256(ptr, EMPTY_POINTER);
266
+ private name: StoredString = new StoredString(ptr, 0);
267
+ private paused: StoredBoolean = new StoredBoolean(ptr, false);
268
+ private owner: StoredAddress = new StoredAddress(ptr); // Default is Address.zero()
269
+
270
+ // The default is returned when storage slot is empty (never written)
271
+ ```
272
+
273
+ ### Setting Initial Values
274
+
275
+ Set values in `onDeployment`:
276
+
277
+ ```typescript
278
+ public override onDeployment(calldata: Calldata): void {
279
+ // Set initial values
280
+ this._name.value = calldata.readString();
281
+ this._symbol.value = calldata.readString();
282
+ this._totalSupply.value = calldata.readU256();
283
+ this._owner.value = Blockchain.tx.origin;
284
+ }
285
+ ```
286
+
287
+ ## Solidity vs OPNet Comparison
288
+
289
+ ### Quick Reference Table
290
+
291
+ | Solidity | OPNet | Default Value |
292
+ |----------|-------|---------------|
293
+ | `uint256 public value;` | `StoredU256` | `u256.Zero` |
294
+ | `uint64[4] packed;` | `StoredU64` | `[0, 0, 0, 0]` |
295
+ | `uint32[8] packed;` | `StoredU32` | `[0, 0, 0, 0, 0, 0, 0, 0]` |
296
+ | `string public name;` | `StoredString` | `""` |
297
+ | `bool public paused;` | `StoredBoolean` | `false` |
298
+ | `address public owner;` | `StoredAddress` | `Address.zero()` |
299
+
300
+ > **Note:** `StoredU64` and `StoredU32` pack multiple values into a single storage slot for efficiency. For single-value storage, use `StoredU256` with appropriate conversions.
301
+
302
+ ### Operations Comparison
303
+
304
+ | Operation | Solidity | OPNet |
305
+ |-----------|----------|-------|
306
+ | Declare state variable | `uint256 public value;` | `private _value: StoredU256 = new StoredU256(ptr, EMPTY_POINTER);` |
307
+ | Read value | `value` or `this.value` | `this._value.value` |
308
+ | Write value | `value = newValue;` | `this._value.value = newValue;` |
309
+ | Increment | `value++;` | `this._value.value = SafeMath.add(this._value.value, u256.One);` |
310
+ | Decrement | `value--;` | `this._value.value = SafeMath.sub(this._value.value, u256.One);` |
311
+ | Add amount | `value += amount;` | `this._value.value = SafeMath.add(this._value.value, amount);` |
312
+ | Check zero | `value == 0` | `this._value.value.isZero()` |
313
+ | Compare | `value > other` | `this._value.value > other` |
314
+ | Set in constructor | `value = initial;` | Use `onDeployment()` |
315
+ | Public getter | Automatic | Must define manually |
316
+
317
+ ### Declaration Patterns
318
+
319
+ | Solidity Pattern | OPNet Equivalent |
320
+ |------------------|------------------|
321
+ | `uint256 public totalSupply;` | `private totalSupplyPtr: u16 = Blockchain.nextPointer;`<br>`private _totalSupply: StoredU256 = new StoredU256(this.totalSupplyPtr, EMPTY_POINTER);` |
322
+ | `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";` |
323
+ | `bool public paused = false;` | `private pausedPtr: u16 = Blockchain.nextPointer;`<br>`private _paused: StoredBoolean = new StoredBoolean(this.pausedPtr, false);` |
324
+ | `address public owner;` | `private ownerPtr: u16 = Blockchain.nextPointer;`<br>`private _owner: StoredAddress = new StoredAddress(this.ownerPtr);` |
325
+
326
+ ### Full Example Comparison
327
+
328
+ ```solidity
329
+ // Solidity
330
+ contract Token {
331
+ string public name; // slot 0
332
+ uint256 public supply; // slot 1
333
+ bool public paused; // slot 2 (packed)
334
+
335
+ constructor(string memory _name, uint256 _supply) {
336
+ name = _name;
337
+ supply = _supply;
338
+ }
339
+ }
340
+ ```
341
+
342
+ ```typescript
343
+ // OPNet
344
+ @final
345
+ export class Token extends OP_NET {
346
+ private namePointer: u16 = Blockchain.nextPointer;
347
+ private supplyPointer: u16 = Blockchain.nextPointer;
348
+ private pausedPointer: u16 = Blockchain.nextPointer;
349
+
350
+ private _name: StoredString = new StoredString(this.namePointer, 0);
351
+ private _supply: StoredU256 = new StoredU256(this.supplyPointer, EMPTY_POINTER);
352
+ private _paused: StoredBoolean = new StoredBoolean(this.pausedPointer, false);
353
+
354
+ public override onDeployment(calldata: Calldata): void {
355
+ this._name.value = calldata.readString();
356
+ this._supply.value = calldata.readU256();
357
+ }
358
+
359
+ // Manual getter
360
+ public name(_calldata: Calldata): BytesWriter {
361
+ const writer = new BytesWriter(256);
362
+ writer.writeString(this._name.value);
363
+ return writer;
364
+ }
365
+ }
366
+ ```
367
+
368
+ ## Side-by-Side Code Examples
369
+
370
+ ### Counter Contract
371
+
372
+ **Solidity:**
373
+ ```solidity
374
+ contract Counter {
375
+ uint256 public count;
376
+
377
+ function increment() external {
378
+ count++;
379
+ }
380
+
381
+ function decrement() external {
382
+ require(count > 0, "Cannot go below zero");
383
+ count--;
384
+ }
385
+
386
+ function add(uint256 amount) external {
387
+ count += amount;
388
+ }
389
+
390
+ function reset() external {
391
+ count = 0;
392
+ }
393
+ }
394
+ ```
395
+
396
+ **OPNet:**
397
+ ```typescript
398
+ @final
399
+ export class Counter extends OP_NET {
400
+ private countPointer: u16 = Blockchain.nextPointer;
401
+ private _count: StoredU256 = new StoredU256(this.countPointer, EMPTY_POINTER);
402
+
403
+ public increment(_calldata: Calldata): BytesWriter {
404
+ this._count.value = SafeMath.add(this._count.value, u256.One);
405
+ return new BytesWriter(0);
406
+ }
407
+
408
+ public decrement(_calldata: Calldata): BytesWriter {
409
+ if (this._count.value.isZero()) {
410
+ throw new Revert('Cannot go below zero');
411
+ }
412
+ this._count.value = SafeMath.sub(this._count.value, u256.One);
413
+ return new BytesWriter(0);
414
+ }
415
+
416
+ public add(calldata: Calldata): BytesWriter {
417
+ const amount = calldata.readU256();
418
+ this._count.value = SafeMath.add(this._count.value, amount);
419
+ return new BytesWriter(0);
420
+ }
421
+
422
+ public reset(_calldata: Calldata): BytesWriter {
423
+ this._count.value = u256.Zero;
424
+ return new BytesWriter(0);
425
+ }
426
+
427
+ public count(_calldata: Calldata): BytesWriter {
428
+ const writer = new BytesWriter(32);
429
+ writer.writeU256(this._count.value);
430
+ return writer;
431
+ }
432
+ }
433
+ ```
434
+
435
+ ### Ownable Contract
436
+
437
+ **Solidity:**
438
+ ```solidity
439
+ contract Ownable {
440
+ address public owner;
441
+ bool public paused;
442
+
443
+ modifier onlyOwner() {
444
+ require(msg.sender == owner, "Not owner");
445
+ _;
446
+ }
447
+
448
+ constructor() {
449
+ owner = msg.sender;
450
+ }
451
+
452
+ function transferOwnership(address newOwner) external onlyOwner {
453
+ require(newOwner != address(0), "Invalid address");
454
+ owner = newOwner;
455
+ }
456
+
457
+ function pause() external onlyOwner {
458
+ paused = true;
459
+ }
460
+
461
+ function unpause() external onlyOwner {
462
+ paused = false;
463
+ }
464
+ }
465
+ ```
466
+
467
+ **OPNet:**
468
+ ```typescript
469
+ @final
470
+ export class Ownable extends OP_NET {
471
+ private ownerPointer: u16 = Blockchain.nextPointer;
472
+ private pausedPointer: u16 = Blockchain.nextPointer;
473
+
474
+ private _owner: StoredAddress = new StoredAddress(this.ownerPointer);
475
+ private _paused: StoredBoolean = new StoredBoolean(this.pausedPointer, false);
476
+
477
+ public override onDeployment(_calldata: Calldata): void {
478
+ this._owner.value = Blockchain.tx.origin;
479
+ }
480
+
481
+ private onlyOwner(): void {
482
+ if (!Blockchain.tx.sender.equals(this._owner.value)) {
483
+ throw new Revert('Not owner');
484
+ }
485
+ }
486
+
487
+ public transferOwnership(calldata: Calldata): BytesWriter {
488
+ this.onlyOwner();
489
+ const newOwner = calldata.readAddress();
490
+ if (newOwner.equals(Address.zero())) {
491
+ throw new Revert('Invalid address');
492
+ }
493
+ this._owner.value = newOwner;
494
+ return new BytesWriter(0);
495
+ }
496
+
497
+ public pause(_calldata: Calldata): BytesWriter {
498
+ this.onlyOwner();
499
+ this._paused.value = true;
500
+ return new BytesWriter(0);
501
+ }
502
+
503
+ public unpause(_calldata: Calldata): BytesWriter {
504
+ this.onlyOwner();
505
+ this._paused.value = false;
506
+ return new BytesWriter(0);
507
+ }
508
+
509
+ public owner(_calldata: Calldata): BytesWriter {
510
+ const writer = new BytesWriter(32);
511
+ writer.writeAddress(this._owner.value);
512
+ return writer;
513
+ }
514
+
515
+ public paused(_calldata: Calldata): BytesWriter {
516
+ const writer = new BytesWriter(1);
517
+ writer.writeBoolean(this._paused.value);
518
+ return writer;
519
+ }
520
+ }
521
+ ```
522
+
523
+ ## Patterns
524
+
525
+ ### Read-Modify-Write
526
+
527
+ ```typescript
528
+ // Increment counter
529
+ public increment(_calldata: Calldata): BytesWriter {
530
+ const current = this._counter.value;
531
+ this._counter.value = SafeMath.add(current, u256.One);
532
+ return new BytesWriter(0);
533
+ }
534
+
535
+ // Toggle boolean
536
+ public togglePause(_calldata: Calldata): BytesWriter {
537
+ this.onlyDeployer(Blockchain.tx.sender);
538
+ this._paused.value = !this._paused.value;
539
+ return new BytesWriter(0);
540
+ }
541
+ ```
542
+
543
+ ### Conditional Updates
544
+
545
+ ```typescript
546
+ public setOwner(calldata: Calldata): BytesWriter {
547
+ this.onlyDeployer(Blockchain.tx.sender);
548
+
549
+ const newOwner = calldata.readAddress();
550
+
551
+ // Validate before writing
552
+ if (newOwner.equals(Address.zero())) {
553
+ throw new Revert('Invalid owner');
554
+ }
555
+
556
+ // Only write if different
557
+ if (!newOwner.equals(this._owner.value)) {
558
+ this._owner.value = newOwner;
559
+ this.emitEvent(new OwnershipTransferred(this._owner.value, newOwner));
560
+ }
561
+
562
+ return new BytesWriter(0);
563
+ }
564
+ ```
565
+
566
+ ### View Functions
567
+
568
+ ```typescript
569
+ // Return stored value
570
+ public totalSupply(_calldata: Calldata): BytesWriter {
571
+ const writer = new BytesWriter(32);
572
+ writer.writeU256(this._totalSupply.value);
573
+ return writer;
574
+ }
575
+
576
+ // Return multiple values
577
+ public getInfo(_calldata: Calldata): BytesWriter {
578
+ const writer = new BytesWriter(256);
579
+ writer.writeString(this._name.value);
580
+ writer.writeString(this._symbol.value);
581
+ writer.writeU256(this._totalSupply.value);
582
+ writer.writeU8(this._decimals.value);
583
+ return writer;
584
+ }
585
+ ```
586
+
587
+ ## Best Practices
588
+
589
+ ### 1. Initialize All Storage
590
+
591
+ ```typescript
592
+ // Always set initial values in onDeployment
593
+ public override onDeployment(calldata: Calldata): void {
594
+ this._name.value = 'Token';
595
+ this._symbol.value = 'TKN';
596
+ this._decimals.value = 18;
597
+ this._owner.value = Blockchain.tx.origin;
598
+ }
599
+ ```
600
+
601
+ ### 2. Use Meaningful Defaults
602
+
603
+ ```typescript
604
+ // Good: EMPTY_POINTER for uninitialized u256 values
605
+ private counter: StoredU256 = new StoredU256(ptr, EMPTY_POINTER);
606
+
607
+ // Note: Set initial values in onDeployment if needed
608
+ // this._counter.value = u256.fromU64(100);
609
+ ```
610
+
611
+ ### 3. Validate Before Writing
612
+
613
+ ```typescript
614
+ public setLimit(calldata: Calldata): BytesWriter {
615
+ const newLimit = calldata.readU256();
616
+
617
+ // Validate
618
+ if (newLimit.isZero()) {
619
+ throw new Revert('Limit cannot be zero');
620
+ }
621
+
622
+ if (newLimit > u256.fromU64(1000000)) {
623
+ throw new Revert('Limit too high');
624
+ }
625
+
626
+ // Then write
627
+ this._limit.value = newLimit;
628
+ return new BytesWriter(0);
629
+ }
630
+ ```
631
+
632
+ ### 4. Cache Reads in Loops
633
+
634
+ ```typescript
635
+ // Bad: Multiple storage reads
636
+ for (let i = 0; i < count; i++) {
637
+ if (amount > this._balance.value) { // Storage read each iteration
638
+ // ...
639
+ }
640
+ }
641
+
642
+ // Good: Cache the value
643
+ const balance = this._balance.value; // One storage read
644
+ for (let i = 0; i < count; i++) {
645
+ if (amount > balance) {
646
+ // ...
647
+ }
648
+ }
649
+ ```
650
+
651
+ ---
652
+
653
+ **Navigation:**
654
+ - Previous: [BytesWriter/Reader](../types/bytes-writer-reader.md)
655
+ - Next: [Stored Arrays](./stored-arrays.md)