@btc-vision/btc-runtime 1.10.10 → 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 +51 -26
  43. package/runtime/memory/MapOfMap.ts +1 -0
  44. package/LICENSE.md +0 -21
@@ -0,0 +1,1006 @@
1
+ # Plugins
2
+
3
+ Plugins extend contract functionality through lifecycle hooks. They allow modular features that can be shared across contracts.
4
+
5
+ ## Overview
6
+
7
+ ```typescript
8
+ import { Plugin, Blockchain, Calldata, Selector } from '@btc-vision/btc-runtime/runtime';
9
+
10
+ // Create a plugin by extending Plugin class
11
+ class MyPlugin extends Plugin {
12
+ public override onDeployment(calldata: Calldata): void {
13
+ // Called during contract deployment
14
+ }
15
+
16
+ public override onExecutionStarted(selector: Selector, calldata: Calldata): void {
17
+ // Called before each method execution
18
+ }
19
+
20
+ public override onExecutionCompleted(selector: Selector, calldata: Calldata): void {
21
+ // Called after each successful method execution
22
+ }
23
+ }
24
+
25
+ // Register with contract
26
+ Blockchain.registerPlugin(new MyPlugin());
27
+ ```
28
+
29
+ ## Plugin Lifecycle
30
+
31
+ Plugins are initialized when the contract is deployed and can intercept method calls:
32
+
33
+ ```mermaid
34
+ ---
35
+ config:
36
+ theme: dark
37
+ ---
38
+ sequenceDiagram
39
+ participant Deployer as 👤 Deployer
40
+ participant Contract as Contract
41
+ participant Plugin as Plugin
42
+ participant Blockchain as OPNet Runtime
43
+
44
+ Note over Deployer,Blockchain: Contract Deployment
45
+ Deployer->>Contract: constructor()
46
+ Contract->>Plugin: new Plugin()
47
+ Contract->>Blockchain: registerPlugin(plugin)
48
+
49
+ Deployer->>Contract: deploy(calldata)
50
+ Blockchain->>Plugin: onDeployment(calldata)
51
+ Note over Plugin: Initialize plugin state
52
+ Plugin-->>Blockchain: complete
53
+
54
+ Blockchain->>Contract: onDeployment(calldata)
55
+ Note over Contract: Initialize contract state
56
+ Contract-->>Blockchain: complete
57
+
58
+ Note over Deployer,Blockchain: Method Execution
59
+ Deployer->>Contract: method(calldata)
60
+
61
+ Blockchain->>Plugin: onExecutionStarted(selector, calldata)
62
+ Note over Plugin: Pre-execution checks<br/>(access control, pausing, etc.)
63
+ Plugin-->>Blockchain: continue
64
+
65
+ Blockchain->>Contract: method executes
66
+ Note over Contract: Core business logic
67
+ Contract-->>Blockchain: result
68
+
69
+ Blockchain->>Plugin: onExecutionCompleted(selector, calldata)
70
+ Note over Plugin: Post-execution tasks<br/>(metrics, logging, etc.)
71
+ Plugin-->>Blockchain: complete
72
+
73
+ Blockchain-->>Deployer: return result
74
+ ```
75
+
76
+ ## Plugin Interface
77
+
78
+ The base Plugin class that all plugins extend:
79
+
80
+ ```mermaid
81
+ ---
82
+ config:
83
+ theme: dark
84
+ ---
85
+ classDiagram
86
+ class Plugin {
87
+ <<base>>
88
+ +onDeployment(calldata: Calldata) void
89
+ +onExecutionStarted(selector: Selector, calldata: Calldata) void
90
+ +onExecutionCompleted(selector: Selector, calldata: Calldata) void
91
+ }
92
+
93
+ class RoleBasedAccessPlugin {
94
+ -rolesPointer: u16
95
+ -roles: AddressMemoryMap
96
+ +hasRole(account: Address, role: u256) bool
97
+ +grantRole(account: Address, role: u256) void
98
+ +revokeRole(account: Address, role: u256) void
99
+ }
100
+
101
+ class PausablePlugin {
102
+ -pausedPointer: u16
103
+ -_paused: StoredBoolean
104
+ +paused: bool
105
+ +pause() void
106
+ +unpause() void
107
+ }
108
+
109
+ class FeeCollectorPlugin {
110
+ -feeRecipientPointer: u16
111
+ -feePercentPointer: u16
112
+ +calculateFee(amount: u256) u256
113
+ +setFeeRecipient(recipient: Address) void
114
+ +setFeePercent(percent: u256) void
115
+ }
116
+
117
+ Plugin <|-- RoleBasedAccessPlugin
118
+ Plugin <|-- PausablePlugin
119
+ Plugin <|-- FeeCollectorPlugin
120
+ ```
121
+
122
+ ### Base Plugin Class
123
+
124
+ The `Plugin` class provides three lifecycle hooks:
125
+
126
+ ```typescript
127
+ export class Plugin {
128
+ // Called once during contract deployment, before contract's onDeployment
129
+ public onDeployment(_calldata: Calldata): void {}
130
+
131
+ // Called before each method execution
132
+ public onExecutionStarted(_selector: Selector, _calldata: Calldata): void {}
133
+
134
+ // Called after each successful method execution
135
+ public onExecutionCompleted(_selector: Selector, _calldata: Calldata): void {}
136
+ }
137
+ ```
138
+
139
+ ### Implementing a Plugin
140
+
141
+ ```typescript
142
+ import {
143
+ Plugin,
144
+ Calldata,
145
+ Selector,
146
+ Blockchain,
147
+ Revert
148
+ } from '@btc-vision/btc-runtime/runtime';
149
+
150
+ class LoggingPlugin extends Plugin {
151
+ public override onDeployment(calldata: Calldata): void {
152
+ // Log deployment
153
+ }
154
+
155
+ public override onExecutionStarted(selector: Selector, calldata: Calldata): void {
156
+ // Log method call before execution
157
+ }
158
+
159
+ public override onExecutionCompleted(selector: Selector, calldata: Calldata): void {
160
+ // Log method completion
161
+ }
162
+ }
163
+ ```
164
+
165
+ ## Registration
166
+
167
+ ### In Contract Constructor
168
+
169
+ ```typescript
170
+ import { OP_NET, Blockchain } from '@btc-vision/btc-runtime/runtime';
171
+
172
+ @final
173
+ export class MyContract extends OP_NET {
174
+ private loggingPlugin: LoggingPlugin;
175
+
176
+ public constructor() {
177
+ super();
178
+
179
+ // Create and register plugins
180
+ this.loggingPlugin = new LoggingPlugin();
181
+ Blockchain.registerPlugin(this.loggingPlugin);
182
+ }
183
+ }
184
+ ```
185
+
186
+ ### Conditional Registration in onDeployment
187
+
188
+ ```typescript
189
+ public override onDeployment(calldata: Calldata): void {
190
+ const enableLogging = calldata.readBoolean();
191
+
192
+ if (enableLogging) {
193
+ Blockchain.registerPlugin(new LoggingPlugin());
194
+ }
195
+
196
+ // Continue with normal deployment
197
+ super.onDeployment(calldata);
198
+ }
199
+ ```
200
+
201
+ ## Role-Based Access Control
202
+
203
+ This diagram shows how the RBAC plugin intercepts calls and checks permissions:
204
+
205
+ ```mermaid
206
+ ---
207
+ config:
208
+ theme: dark
209
+ ---
210
+ flowchart LR
211
+ subgraph OPNet["OPNet Plugin-Based Access Control"]
212
+ A["👤 User calls method"] --> B["onExecutionStarted"]
213
+ B --> C{"Method requires role?"}
214
+
215
+ C -->|"No"| D["Allow execution"]
216
+
217
+ C -->|"Yes"| E["Get required role for selector"]
218
+ E --> F["Load user's roles from storage"]
219
+ F --> G{"User has role?<br/>Bitwise AND check"}
220
+
221
+ G -->|"Yes"| H["Allow execution"]
222
+ G -->|"No"| I["Revert: Missing required role"]
223
+
224
+ D --> J["Method executes"]
225
+ H --> J
226
+
227
+ subgraph RoleBits["Role Enum - Powers of 2"]
228
+ R1["Role.ADMIN = 1"]
229
+ R2["Role.MINTER = 2"]
230
+ R3["Role.PAUSER = 4"]
231
+ R4["Role.OPERATOR = 8"]
232
+ end
233
+
234
+ subgraph BitwiseOps["Bitwise Operations"]
235
+ B1["Grant: currentRoles OR role"]
236
+ B2["Check: currentRoles AND role != 0"]
237
+ B3["Revoke: currentRoles AND NOT role"]
238
+ end
239
+ end
240
+ ```
241
+
242
+ ### Access Control Plugin Implementation
243
+
244
+ Use an enum with bit flags for role management (powers of 2):
245
+
246
+ ```typescript
247
+ import { u256 } from '@btc-vision/as-bignum/assembly';
248
+ import {
249
+ Plugin,
250
+ Calldata,
251
+ Selector,
252
+ Blockchain,
253
+ Address,
254
+ Revert,
255
+ SafeMath,
256
+ StoredU256,
257
+ AddressMemoryMap,
258
+ encodeSelector
259
+ } from '@btc-vision/btc-runtime/runtime';
260
+
261
+ // Define method selectors (sha256 first 4 bytes of method signature)
262
+ const MINT_SELECTOR: u32 = 0x40c10f19; // mint(address,uint256)
263
+ const PAUSE_SELECTOR: u32 = 0x8456cb59; // pause()
264
+ const UNPAUSE_SELECTOR: u32 = 0x3f4ba83a; // unpause()
265
+ const SET_OPERATOR_SELECTOR: u32 = 0xb3ab15fb; // setOperator(address)
266
+
267
+ // Role enum (bit flags - must be powers of 2)
268
+ enum Role {
269
+ ADMIN = 1, // 2^0
270
+ MINTER = 2, // 2^1
271
+ PAUSER = 4, // 2^2
272
+ OPERATOR = 8 // 2^3
273
+ }
274
+
275
+ class RoleBasedAccessPlugin extends Plugin {
276
+ private rolesPointer: u16;
277
+ private roles: AddressMemoryMap;
278
+
279
+ public constructor(pointer: u16) {
280
+ super();
281
+ this.rolesPointer = pointer;
282
+ this.roles = new AddressMemoryMap(this.rolesPointer);
283
+ }
284
+
285
+ public override onDeployment(calldata: Calldata): void {
286
+ // Grant admin role to deployer
287
+ const deployer = Blockchain.tx.origin;
288
+ this.grantRole(deployer, u256.fromU64(Role.ADMIN));
289
+ }
290
+
291
+ public override onExecutionStarted(selector: Selector, calldata: Calldata): void {
292
+ const requiredRole = this.getRequiredRole(selector);
293
+ if (!requiredRole.isZero()) {
294
+ const sender = Blockchain.tx.sender;
295
+ if (!this.hasRole(sender, requiredRole)) {
296
+ throw new Revert('Missing required role');
297
+ }
298
+ }
299
+ }
300
+
301
+ public override onExecutionCompleted(selector: Selector, calldata: Calldata): void {
302
+ // No-op for this plugin
303
+ }
304
+
305
+ public hasRole(account: Address, role: u256): bool {
306
+ const userRoles = this.roles.get(account);
307
+ // Check if role bit is set using bitwise AND
308
+ return !SafeMath.and(userRoles, role).isZero();
309
+ }
310
+
311
+ public grantRole(account: Address, role: u256): void {
312
+ const currentRoles = this.roles.get(account);
313
+ // Set role bit using bitwise OR
314
+ this.roles.set(account, SafeMath.or(currentRoles, role));
315
+ }
316
+
317
+ public revokeRole(account: Address, role: u256): void {
318
+ const currentRoles = this.roles.get(account);
319
+ // Clear role bit using AND with inverted role
320
+ const invertedRole = SafeMath.xor(role, u256.Max);
321
+ this.roles.set(account, SafeMath.and(currentRoles, invertedRole));
322
+ }
323
+
324
+ private getRequiredRole(selector: Selector): u256 {
325
+ // Map methods to required roles
326
+ switch (selector) {
327
+ case MINT_SELECTOR:
328
+ return u256.fromU64(Role.MINTER);
329
+ case PAUSE_SELECTOR:
330
+ case UNPAUSE_SELECTOR:
331
+ return u256.fromU64(Role.PAUSER);
332
+ case SET_OPERATOR_SELECTOR:
333
+ return u256.fromU64(Role.ADMIN);
334
+ default:
335
+ return u256.Zero; // No role required
336
+ }
337
+ }
338
+ }
339
+ ```
340
+
341
+ ## Pausable Plugin
342
+
343
+ ```typescript
344
+ import {
345
+ Plugin,
346
+ Calldata,
347
+ Selector,
348
+ Blockchain,
349
+ Revert,
350
+ StoredBoolean,
351
+ encodeSelector
352
+ } from '@btc-vision/btc-runtime/runtime';
353
+
354
+ // Define method selectors (sha256 first 4 bytes of method signature)
355
+ const BALANCE_OF_SELECTOR: u32 = 0x70a08231; // balanceOf(address)
356
+ const TOTAL_SUPPLY_SELECTOR: u32 = 0x18160ddd; // totalSupply()
357
+ const NAME_SELECTOR: u32 = 0x06fdde03; // name()
358
+ const SYMBOL_SELECTOR: u32 = 0x95d89b41; // symbol()
359
+ const PAUSE_SELECTOR: u32 = 0x8456cb59; // pause()
360
+ const UNPAUSE_SELECTOR: u32 = 0x3f4ba83a; // unpause()
361
+
362
+ class PausablePlugin extends Plugin {
363
+ private pausedPointer: u16;
364
+ private _paused: StoredBoolean;
365
+
366
+ public constructor(pointer: u16) {
367
+ super();
368
+ this.pausedPointer = pointer;
369
+ this._paused = new StoredBoolean(this.pausedPointer, false);
370
+ }
371
+
372
+ public override onDeployment(calldata: Calldata): void {
373
+ // Start unpaused by default
374
+ }
375
+
376
+ public override onExecutionStarted(selector: Selector, calldata: Calldata): void {
377
+ // Skip pause check for view methods
378
+ if (this.isViewMethod(selector)) return;
379
+
380
+ // Skip pause check for admin methods
381
+ if (this.isAdminMethod(selector)) return;
382
+
383
+ if (this._paused.value) {
384
+ throw new Revert('Contract is paused');
385
+ }
386
+ }
387
+
388
+ public override onExecutionCompleted(selector: Selector, calldata: Calldata): void {
389
+ // No-op for this plugin
390
+ }
391
+
392
+ public get paused(): bool {
393
+ return this._paused.value;
394
+ }
395
+
396
+ public pause(): void {
397
+ this._paused.value = true;
398
+ }
399
+
400
+ public unpause(): void {
401
+ this._paused.value = false;
402
+ }
403
+
404
+ private isViewMethod(selector: Selector): bool {
405
+ switch (selector) {
406
+ case BALANCE_OF_SELECTOR:
407
+ case TOTAL_SUPPLY_SELECTOR:
408
+ case NAME_SELECTOR:
409
+ case SYMBOL_SELECTOR:
410
+ return true;
411
+ default:
412
+ return false;
413
+ }
414
+ }
415
+
416
+ private isAdminMethod(selector: Selector): bool {
417
+ switch (selector) {
418
+ case PAUSE_SELECTOR:
419
+ case UNPAUSE_SELECTOR:
420
+ return true;
421
+ default:
422
+ return false;
423
+ }
424
+ }
425
+ }
426
+ ```
427
+
428
+ ## Fee Collector Plugin
429
+
430
+ ```typescript
431
+ import { u256 } from '@btc-vision/as-bignum/assembly';
432
+ import {
433
+ Plugin,
434
+ Calldata,
435
+ Selector,
436
+ Address,
437
+ SafeMath,
438
+ StoredAddress,
439
+ StoredU256,
440
+ EMPTY_POINTER
441
+ } from '@btc-vision/btc-runtime/runtime';
442
+
443
+ class FeeCollectorPlugin extends Plugin {
444
+ private feeRecipientPointer: u16;
445
+ private feePercentPointer: u16;
446
+ private _feeRecipient: StoredAddress;
447
+ private _feePercent: StoredU256; // Basis points (100 = 1%)
448
+
449
+ public constructor(recipientPointer: u16, percentPointer: u16) {
450
+ super();
451
+ this.feeRecipientPointer = recipientPointer;
452
+ this.feePercentPointer = percentPointer;
453
+ this._feeRecipient = new StoredAddress(this.feeRecipientPointer, Address.zero());
454
+ this._feePercent = new StoredU256(this.feePercentPointer, EMPTY_POINTER);
455
+ }
456
+
457
+ public override onDeployment(calldata: Calldata): void {
458
+ // Fee configuration set separately
459
+ }
460
+
461
+ public override onExecutionStarted(selector: Selector, calldata: Calldata): void {
462
+ // No pre-execution logic needed
463
+ }
464
+
465
+ public override onExecutionCompleted(selector: Selector, calldata: Calldata): void {
466
+ // Could track fees collected per method
467
+ }
468
+
469
+ public setFeeRecipient(recipient: Address): void {
470
+ this._feeRecipient.value = recipient;
471
+ }
472
+
473
+ public setFeePercent(percent: u256): void {
474
+ this._feePercent.value = percent;
475
+ }
476
+
477
+ public calculateFee(amount: u256): u256 {
478
+ return SafeMath.div(
479
+ SafeMath.mul(amount, this._feePercent.value),
480
+ u256.fromU64(10000) // Basis points denominator
481
+ );
482
+ }
483
+
484
+ public get feeRecipient(): Address {
485
+ return this._feeRecipient.value;
486
+ }
487
+
488
+ public get feePercent(): u256 {
489
+ return this._feePercent.value;
490
+ }
491
+ }
492
+ ```
493
+
494
+ ## Plugin Communication
495
+
496
+ ### Using Plugins in Contracts
497
+
498
+ ```typescript
499
+ import { u256 } from '@btc-vision/as-bignum/assembly';
500
+ import {
501
+ OP_NET,
502
+ Blockchain,
503
+ Calldata,
504
+ BytesWriter,
505
+ Address,
506
+ SafeMath,
507
+ ABIDataTypes
508
+ } from '@btc-vision/btc-runtime/runtime';
509
+
510
+ @final
511
+ export class MyContract extends OP_NET {
512
+ private feePlugin: FeeCollectorPlugin;
513
+
514
+ public constructor() {
515
+ super();
516
+ // Use nextPointer for storage allocation
517
+ this.feePlugin = new FeeCollectorPlugin(
518
+ Blockchain.nextPointer,
519
+ Blockchain.nextPointer
520
+ );
521
+ Blockchain.registerPlugin(this.feePlugin);
522
+ }
523
+
524
+ @method(
525
+ { name: 'to', type: ABIDataTypes.ADDRESS },
526
+ { name: 'amount', type: ABIDataTypes.UINT256 },
527
+ )
528
+ public transfer(calldata: Calldata): BytesWriter {
529
+ const to = calldata.readAddress();
530
+ const amount = calldata.readU256();
531
+
532
+ // Use plugin to calculate fee
533
+ const fee = this.feePlugin.calculateFee(amount);
534
+ const netAmount = SafeMath.sub(amount, fee);
535
+
536
+ // Transfer net amount to recipient
537
+ this._transfer(Blockchain.tx.sender, to, netAmount);
538
+
539
+ // Transfer fee to collector
540
+ if (!fee.isZero()) {
541
+ this._transfer(Blockchain.tx.sender, this.feePlugin.feeRecipient, fee);
542
+ }
543
+
544
+ return new BytesWriter(0);
545
+ }
546
+ }
547
+ ```
548
+
549
+ ## Lifecycle Hooks
550
+
551
+ ### Execution Order
552
+
553
+ ```
554
+ 1. Plugin.onDeployment (for each registered plugin, in order)
555
+ 2. Contract.onDeployment
556
+
557
+ Then for each method call:
558
+ 1. Plugin.onExecutionStarted (for each registered plugin, in order)
559
+ 2. Contract.onExecutionStarted
560
+ 3. Contract method executes
561
+ 4. Plugin.onExecutionCompleted (for each registered plugin, in order)
562
+ 5. Contract.onExecutionCompleted
563
+ ```
564
+
565
+ ### Metrics Plugin Example
566
+
567
+ ```typescript
568
+ import {
569
+ Plugin,
570
+ Calldata,
571
+ Selector,
572
+ StoredU256,
573
+ SafeMath,
574
+ EMPTY_POINTER
575
+ } from '@btc-vision/btc-runtime/runtime';
576
+ import { u256 } from '@btc-vision/as-bignum/assembly';
577
+
578
+ class MetricsPlugin extends Plugin {
579
+ private totalCallsPointer: u16;
580
+ private _totalCalls: StoredU256;
581
+
582
+ public constructor(pointer: u16) {
583
+ super();
584
+ this.totalCallsPointer = pointer;
585
+ this._totalCalls = new StoredU256(this.totalCallsPointer, EMPTY_POINTER);
586
+ }
587
+
588
+ public override onDeployment(calldata: Calldata): void {
589
+ // Initialize metrics
590
+ }
591
+
592
+ public override onExecutionStarted(selector: Selector, calldata: Calldata): void {
593
+ // Increment call counter
594
+ this._totalCalls.value = SafeMath.add(this._totalCalls.value, u256.One);
595
+ }
596
+
597
+ public override onExecutionCompleted(selector: Selector, calldata: Calldata): void {
598
+ // Could track successful completions separately
599
+ }
600
+
601
+ public get totalCalls(): u256 {
602
+ return this._totalCalls.value;
603
+ }
604
+ }
605
+ ```
606
+
607
+ ## Solidity vs OPNet: Plugin System Comparison
608
+
609
+ OPNet plugins provide a more flexible and powerful alternative to Solidity's inheritance and modifier patterns. They enable cross-cutting concerns without tight coupling.
610
+
611
+ ### Feature Comparison Table
612
+
613
+ | Feature | Solidity/EVM | OPNet | OPNet Advantage |
614
+ |---------|--------------|-------|-----------------|
615
+ | **Code Reuse Pattern** | Inheritance | Composition (Plugins) | Flexible, no diamond problem |
616
+ | **Pre-Execution Hooks** | Modifiers | `onExecutionStarted` | Centralized, selector-aware |
617
+ | **Post-Execution Hooks** | Limited (manual) | `onExecutionCompleted` | Automatic after success |
618
+ | **Deployment Hooks** | Constructor only | `onDeployment` per plugin | Modular initialization |
619
+ | **Method Interception** | Modifier per function | Selector-based routing | One plugin, all methods |
620
+ | **Multiple Behaviors** | Multiple inheritance | Multiple plugins | No conflicts |
621
+ | **Dynamic Registration** | Not possible | Runtime registration | Conditional features |
622
+ | **Execution Order** | Modifier stack | Plugin registration order | Explicit control |
623
+
624
+ ### Pattern Mapping Table
625
+
626
+ | Solidity Pattern | OPNet Plugin Equivalent | Improvement |
627
+ |-----------------|------------------------|-------------|
628
+ | OpenZeppelin `Ownable` | `RoleBasedAccessPlugin` with ADMIN role | Multi-role support |
629
+ | OpenZeppelin `Pausable` | `PausablePlugin` | Selector-specific pausing |
630
+ | OpenZeppelin `AccessControl` | `RoleBasedAccessPlugin` | Bit-flag roles, efficient storage |
631
+ | `nonReentrant` modifier | Reentrancy guard in `onExecutionStarted` | Global protection |
632
+ | Function modifiers | `onExecutionStarted` hook | Centralized logic |
633
+ | `Initializable` | `onDeployment` hook | Per-plugin initialization |
634
+ | OpenZeppelin `ERC20Snapshot` | Metrics plugin pattern | Flexible tracking |
635
+
636
+ ### Capability Matrix
637
+
638
+ | Capability | Solidity | OPNet |
639
+ |------------|:--------:|:-----:|
640
+ | Pre-execution hooks | Modifiers (per-function) | Plugins (centralized) |
641
+ | Post-execution hooks | Manual | Built-in |
642
+ | Deployment initialization | Constructor | Per-plugin onDeployment |
643
+ | Selector-based routing | Manual switch | Built-in selector parameter |
644
+ | Dynamic feature toggles | Storage + modifiers | Conditional plugin registration |
645
+ | Cross-cutting logging | Manual in each function | Single metrics plugin |
646
+ | Role-based access | Multiple modifiers | Single plugin, bitwise roles |
647
+ | Pausable with exceptions | Complex modifiers | Selector whitelist |
648
+
649
+ ### Inheritance vs Composition
650
+
651
+ ```mermaid
652
+ ---
653
+ config:
654
+ theme: dark
655
+ ---
656
+ flowchart TB
657
+ subgraph Solidity["Solidity - Inheritance Chain"]
658
+ S1["Contract"] --> S2["Ownable"]
659
+ S1 --> S3["Pausable"]
660
+ S1 --> S4["AccessControl"]
661
+ S2 --> S5["Context"]
662
+ S3 --> S5
663
+ S4 --> S5
664
+ S6["Diamond Problem Risk"]
665
+ end
666
+
667
+ subgraph OPNet["OPNet - Plugin Composition"]
668
+ O1["Contract"] --> O2["OP_NET base"]
669
+ O3["AccessPlugin"] -.->|"registered"| O1
670
+ O4["PausablePlugin"] -.->|"registered"| O1
671
+ O5["FeePlugin"] -.->|"registered"| O1
672
+ O6["No Inheritance Conflicts"]
673
+ end
674
+ ```
675
+
676
+ ### Code Complexity Comparison
677
+
678
+ | Aspect | Solidity | OPNet |
679
+ |--------|----------|-------|
680
+ | Adding access control | Import + inherit + add modifiers | Register plugin |
681
+ | Adding pausable | Import + inherit + add modifiers | Register plugin |
682
+ | Combining both | Multiple inheritance + modifier stacking | Register both plugins |
683
+ | Method-specific rules | Separate modifier per method | Selector switch in plugin |
684
+ | Adding new role | Modify contract, redeploy | Update plugin role mapping |
685
+
686
+ ### Access Control Comparison
687
+
688
+ #### Solidity: OpenZeppelin Inheritance
689
+
690
+ ```solidity
691
+ // Solidity with OpenZeppelin - Inheritance-based
692
+ import "@openzeppelin/contracts/access/Ownable.sol";
693
+ import "@openzeppelin/contracts/access/AccessControl.sol";
694
+
695
+ contract MyContract is Ownable, AccessControl {
696
+ bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
697
+ bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
698
+
699
+ constructor() {
700
+ _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
701
+ }
702
+
703
+ // Must add modifier to EVERY protected function
704
+ function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
705
+ // ...
706
+ }
707
+
708
+ function pause() external onlyRole(PAUSER_ROLE) {
709
+ // ...
710
+ }
711
+
712
+ function adminOnly() external onlyOwner {
713
+ // ...
714
+ }
715
+
716
+ // Limitations:
717
+ // - Must add modifier to each function
718
+ // - Role checks scattered across contract
719
+ // - String-based role hashes (inefficient)
720
+ // - Complex inheritance hierarchy
721
+ }
722
+ ```
723
+
724
+ #### OPNet: Plugin-Based Composition
725
+
726
+ ```typescript
727
+ // OPNet with Plugin - Composition-based
728
+ @final
729
+ export class MyContract extends OP_NET {
730
+ private accessPlugin: RoleBasedAccessPlugin;
731
+
732
+ public constructor() {
733
+ super();
734
+ // Single plugin handles ALL access control
735
+ this.accessPlugin = new RoleBasedAccessPlugin(Blockchain.nextPointer);
736
+ Blockchain.registerPlugin(this.accessPlugin);
737
+ }
738
+
739
+ @method()
740
+ public mint(_calldata: Calldata): BytesWriter {
741
+ // Plugin automatically checks MINTER role
742
+ // based on selector mapping - no modifier needed!
743
+ return new BytesWriter(0);
744
+ }
745
+
746
+ @method()
747
+ public pause(_calldata: Calldata): BytesWriter {
748
+ // Plugin automatically checks PAUSER role
749
+ return new BytesWriter(0);
750
+ }
751
+
752
+ @method()
753
+ public adminOnly(_calldata: Calldata): BytesWriter {
754
+ // Plugin automatically checks ADMIN role
755
+ return new BytesWriter(0);
756
+ }
757
+
758
+ // Advantages:
759
+ // - No modifiers on individual functions
760
+ // - Centralized access control logic
761
+ // - Bitwise roles (efficient storage)
762
+ // - Role-to-selector mapping in one place
763
+ }
764
+ ```
765
+
766
+ ### Pausable Comparison
767
+
768
+ #### Solidity: Modifier Per Function
769
+
770
+ ```solidity
771
+ // Solidity with OpenZeppelin
772
+ import "@openzeppelin/contracts/security/Pausable.sol";
773
+
774
+ contract MyContract is Pausable {
775
+ // Must add whenNotPaused to EVERY pausable function
776
+ function transfer(address to, uint256 amount) external whenNotPaused {
777
+ // ...
778
+ }
779
+
780
+ function approve(address spender, uint256 amount) external whenNotPaused {
781
+ // ...
782
+ }
783
+
784
+ // View functions don't need modifier (manual decision)
785
+ function balanceOf(address owner) external view returns (uint256) {
786
+ // ...
787
+ }
788
+
789
+ function pause() external onlyOwner {
790
+ _pause();
791
+ }
792
+
793
+ function unpause() external onlyOwner {
794
+ _unpause();
795
+ }
796
+
797
+ // Limitations:
798
+ // - Must remember to add modifier to each function
799
+ // - Easy to forget on new functions
800
+ // - No automatic view function detection
801
+ }
802
+ ```
803
+
804
+ #### OPNet: Automatic Selector-Based Pausing
805
+
806
+ ```typescript
807
+ // OPNet with Plugin
808
+ @final
809
+ export class MyContract extends OP_NET {
810
+ private pausablePlugin: PausablePlugin;
811
+
812
+ public constructor() {
813
+ super();
814
+ this.pausablePlugin = new PausablePlugin(Blockchain.nextPointer);
815
+ Blockchain.registerPlugin(this.pausablePlugin);
816
+ }
817
+
818
+ @method(ABIDataTypes.UINT256)
819
+ public transfer(calldata: Calldata): BytesWriter {
820
+ // Plugin automatically checks pause status
821
+ // No modifier needed!
822
+ const amount = calldata.readU256();
823
+ return new BytesWriter(0);
824
+ }
825
+
826
+ @method(ABIDataTypes.ADDRESS)
827
+ public balanceOf(calldata: Calldata): BytesWriter {
828
+ // Plugin knows this is a view method (via selector)
829
+ // Automatically skips pause check
830
+ return new BytesWriter(32);
831
+ }
832
+
833
+ @method()
834
+ public pause(_calldata: Calldata): BytesWriter {
835
+ // Admin methods also whitelisted automatically
836
+ this.pausablePlugin.pause();
837
+ return new BytesWriter(0);
838
+ }
839
+
840
+ // Advantages:
841
+ // - No modifiers needed on individual functions
842
+ // - View methods automatically detected via selector
843
+ // - Admin methods automatically whitelisted
844
+ // - Cannot forget to protect a function
845
+ }
846
+ ```
847
+
848
+ ### Multiple Plugins Example
849
+
850
+ ```typescript
851
+ // OPNet - Composing multiple plugins (no inheritance conflicts)
852
+ @final
853
+ export class CompleteContract extends OP_NET {
854
+ private accessPlugin: RoleBasedAccessPlugin;
855
+ private pausablePlugin: PausablePlugin;
856
+ private feePlugin: FeeCollectorPlugin;
857
+ private metricsPlugin: MetricsPlugin;
858
+
859
+ public constructor() {
860
+ super();
861
+
862
+ // Order matters - security checks first!
863
+ this.accessPlugin = new RoleBasedAccessPlugin(Blockchain.nextPointer);
864
+ Blockchain.registerPlugin(this.accessPlugin); // 1. Check permissions
865
+
866
+ this.pausablePlugin = new PausablePlugin(Blockchain.nextPointer);
867
+ Blockchain.registerPlugin(this.pausablePlugin); // 2. Check pause status
868
+
869
+ this.feePlugin = new FeeCollectorPlugin(
870
+ Blockchain.nextPointer,
871
+ Blockchain.nextPointer
872
+ );
873
+ Blockchain.registerPlugin(this.feePlugin); // 3. Fee calculations
874
+
875
+ this.metricsPlugin = new MetricsPlugin(Blockchain.nextPointer);
876
+ Blockchain.registerPlugin(this.metricsPlugin); // 4. Track metrics
877
+ }
878
+
879
+ // All plugins execute their hooks automatically
880
+ // Execution order: access -> pausable -> fee -> metrics -> method
881
+ }
882
+ ```
883
+
884
+ ### Lifecycle Hook Comparison
885
+
886
+ | Lifecycle Event | Solidity | OPNet |
887
+ |-----------------|----------|-------|
888
+ | Contract deployment | Single constructor | `onDeployment` per plugin + contract |
889
+ | Before method call | Modifiers (manual per function) | `onExecutionStarted` (automatic) |
890
+ | After method call | No built-in hook | `onExecutionCompleted` (automatic) |
891
+ | Error handling | try/catch (limited) | Revert in any hook |
892
+
893
+ ### Why OPNet Plugins?
894
+
895
+ | Solidity Limitation | OPNet Solution |
896
+ |---------------------|----------------|
897
+ | Modifier on every function | Centralized selector-based routing |
898
+ | Multiple inheritance complexity | Simple composition |
899
+ | Diamond problem risk | No inheritance conflicts |
900
+ | String-based role hashes | Efficient bitwise roles |
901
+ | Manual post-execution hooks | Built-in `onExecutionCompleted` |
902
+ | Constructor-only initialization | Per-plugin `onDeployment` |
903
+ | Static feature set | Dynamic plugin registration |
904
+ | Scattered access logic | Centralized in plugins |
905
+
906
+ ## Best Practices
907
+
908
+ ### 1. Single Responsibility
909
+
910
+ ```typescript
911
+ // Good: Focused plugins
912
+ class PausablePlugin extends Plugin { }
913
+ class AccessControlPlugin extends Plugin { }
914
+ class FeeCollectorPlugin extends Plugin { }
915
+
916
+ // Bad: Monolithic plugin
917
+ class EverythingPlugin extends Plugin {
918
+ // Handles pausing, access control, fees, logging...
919
+ }
920
+ ```
921
+
922
+ ### 2. Use `public override` for Hook Methods
923
+
924
+ ```typescript
925
+ class MyPlugin extends Plugin {
926
+ // Always use 'public override' when overriding Plugin methods
927
+ public override onDeployment(calldata: Calldata): void {
928
+ // Implementation
929
+ }
930
+
931
+ public override onExecutionStarted(selector: Selector, calldata: Calldata): void {
932
+ // Implementation
933
+ }
934
+
935
+ public override onExecutionCompleted(selector: Selector, calldata: Calldata): void {
936
+ // Implementation
937
+ }
938
+ }
939
+ ```
940
+
941
+ ### 3. Use Proper Storage
942
+
943
+ ```typescript
944
+ // Good: Use StoredU256, StoredBoolean, etc. for persistent state
945
+ class MyPlugin extends Plugin {
946
+ private pointer: u16;
947
+ private _value: StoredU256;
948
+
949
+ public constructor(pointer: u16) {
950
+ super();
951
+ this.pointer = pointer;
952
+ this._value = new StoredU256(this.pointer, EMPTY_POINTER);
953
+ }
954
+ }
955
+
956
+ // Bad: Regular class fields don't persist across calls
957
+ class BadPlugin extends Plugin {
958
+ private value: u256 = u256.Zero; // This won't persist!
959
+ }
960
+ ```
961
+
962
+ ### 4. Use Role Enum with Bit Flags
963
+
964
+ ```typescript
965
+ // Good: Use enum with bit flags for roles (powers of 2)
966
+ enum Role {
967
+ ADMIN = 1, // 2^0
968
+ MINTER = 2, // 2^1
969
+ PAUSER = 4 // 2^2
970
+ }
971
+
972
+ // Convert enum to u256 for storage/comparison
973
+ const adminRole: u256 = u256.fromU64(Role.ADMIN);
974
+
975
+ // Check role with bitwise AND
976
+ const hasRole = !SafeMath.and(userRoles, u256.fromU64(Role.MINTER)).isZero();
977
+
978
+ // Bad: String-based roles
979
+ // const roles = new Map<string, Set<Address>>(); // Don't do this!
980
+ ```
981
+
982
+ ### 5. Handle Errors Gracefully
983
+
984
+ ```typescript
985
+ public override onExecutionStarted(selector: Selector, calldata: Calldata): void {
986
+ // Throw Revert for access control failures
987
+ if (!this.hasPermission(Blockchain.tx.sender, selector)) {
988
+ throw new Revert('Access denied');
989
+ }
990
+ }
991
+ ```
992
+
993
+ ### 6. Order Plugins Correctly
994
+
995
+ ```typescript
996
+ // Security checks should come first
997
+ Blockchain.registerPlugin(this.accessControl); // Check permissions first
998
+ Blockchain.registerPlugin(this.pausable); // Then pausable
999
+ Blockchain.registerPlugin(this.metrics); // Metrics last
1000
+ ```
1001
+
1002
+ ---
1003
+
1004
+ **Navigation:**
1005
+ - Previous: [Bitcoin Scripts](./bitcoin-scripts.md)
1006
+ - Next: [Basic Token Example](../examples/basic-token.md)