@btc-vision/btc-runtime 1.10.12 → 1.11.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -224
- package/SECURITY.md +38 -191
- package/docs/README.md +28 -0
- package/docs/advanced/contract-upgrades.md +537 -0
- package/docs/advanced/plugins.md +90 -25
- package/docs/api-reference/blockchain.md +48 -14
- package/docs/contracts/op-net-base.md +22 -0
- package/docs/contracts/upgradeable.md +396 -0
- package/docs/core-concepts/blockchain-environment.md +0 -2
- package/docs/types/bytes-writer-reader.md +76 -0
- package/package.json +5 -5
- package/runtime/buffer/BytesReader.ts +90 -3
- package/runtime/buffer/BytesWriter.ts +81 -3
- package/runtime/contracts/OP721.ts +40 -4
- package/runtime/contracts/OP_NET.ts +83 -11
- package/runtime/contracts/Upgradeable.ts +242 -0
- package/runtime/env/BlockchainEnvironment.ts +124 -27
- package/runtime/env/global.ts +24 -0
- package/runtime/events/upgradeable/UpgradeableEvents.ts +41 -0
- package/runtime/generic/AddressMap.ts +20 -18
- package/runtime/generic/ExtendedAddressMap.ts +147 -0
- package/runtime/generic/MapUint8Array.ts +20 -18
- package/runtime/index.ts +8 -0
- package/runtime/plugins/Plugin.ts +34 -0
- package/runtime/plugins/UpgradeablePlugin.ts +279 -0
- package/runtime/storage/BaseStoredString.ts +1 -1
- package/runtime/storage/arrays/StoredPackedArray.ts +4 -0
- package/runtime/types/ExtendedAddress.ts +36 -24
- package/runtime/types/ExtendedAddressCache.ts +27 -0
- package/runtime/types/SafeMath.ts +34 -63
- package/runtime/types/SchnorrSignature.ts +44 -0
- package/runtime/utils/lengths.ts +2 -0
package/docs/advanced/plugins.md
CHANGED
|
@@ -5,7 +5,7 @@ Plugins extend contract functionality through lifecycle hooks. They allow modula
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
|
-
import { Plugin, Blockchain, Calldata, Selector } from '@btc-vision/btc-runtime/runtime';
|
|
8
|
+
import { Plugin, Blockchain, Calldata, Selector, BytesWriter } from '@btc-vision/btc-runtime/runtime';
|
|
9
9
|
|
|
10
10
|
// Create a plugin by extending Plugin class
|
|
11
11
|
class MyPlugin extends Plugin {
|
|
@@ -13,6 +13,10 @@ class MyPlugin extends Plugin {
|
|
|
13
13
|
// Called during contract deployment
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
public override onUpdate(calldata: Calldata): void {
|
|
17
|
+
// Called when contract bytecode is updated
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
public override onExecutionStarted(selector: Selector, calldata: Calldata): void {
|
|
17
21
|
// Called before each method execution
|
|
18
22
|
}
|
|
@@ -20,15 +24,20 @@ class MyPlugin extends Plugin {
|
|
|
20
24
|
public override onExecutionCompleted(selector: Selector, calldata: Calldata): void {
|
|
21
25
|
// Called after each successful method execution
|
|
22
26
|
}
|
|
27
|
+
|
|
28
|
+
public override execute(method: Selector, calldata: Calldata): BytesWriter | null {
|
|
29
|
+
// Handle method selectors - return BytesWriter if handled, null if not
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
// Register with contract
|
|
26
|
-
|
|
35
|
+
this.registerPlugin(new MyPlugin());
|
|
27
36
|
```
|
|
28
37
|
|
|
29
38
|
## Plugin Lifecycle
|
|
30
39
|
|
|
31
|
-
Plugins are initialized when the contract is deployed and can intercept method calls:
|
|
40
|
+
Plugins are initialized when the contract is deployed and can intercept method calls or handle them directly:
|
|
32
41
|
|
|
33
42
|
```mermaid
|
|
34
43
|
---
|
|
@@ -44,7 +53,7 @@ sequenceDiagram
|
|
|
44
53
|
Note over Deployer,Blockchain: Contract Deployment
|
|
45
54
|
Deployer->>Contract: constructor()
|
|
46
55
|
Contract->>Plugin: new Plugin()
|
|
47
|
-
Contract->>
|
|
56
|
+
Contract->>Contract: registerPlugin(plugin)
|
|
48
57
|
|
|
49
58
|
Deployer->>Contract: deploy(calldata)
|
|
50
59
|
Blockchain->>Plugin: onDeployment(calldata)
|
|
@@ -62,9 +71,14 @@ sequenceDiagram
|
|
|
62
71
|
Note over Plugin: Pre-execution checks<br/>(access control, pausing, etc.)
|
|
63
72
|
Plugin-->>Blockchain: continue
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
alt Plugin handles selector
|
|
75
|
+
Blockchain->>Plugin: execute(selector, calldata)
|
|
76
|
+
Plugin-->>Blockchain: BytesWriter result
|
|
77
|
+
else Contract handles selector
|
|
78
|
+
Blockchain->>Contract: method executes
|
|
79
|
+
Note over Contract: Core business logic
|
|
80
|
+
Contract-->>Blockchain: result
|
|
81
|
+
end
|
|
68
82
|
|
|
69
83
|
Blockchain->>Plugin: onExecutionCompleted(selector, calldata)
|
|
70
84
|
Note over Plugin: Post-execution tasks<br/>(metrics, logging, etc.)
|
|
@@ -86,8 +100,10 @@ classDiagram
|
|
|
86
100
|
class Plugin {
|
|
87
101
|
<<base>>
|
|
88
102
|
+onDeployment(calldata: Calldata) void
|
|
103
|
+
+onUpdate(calldata: Calldata) void
|
|
89
104
|
+onExecutionStarted(selector: Selector, calldata: Calldata) void
|
|
90
105
|
+onExecutionCompleted(selector: Selector, calldata: Calldata) void
|
|
106
|
+
+execute(method: Selector, calldata: Calldata) BytesWriter|null
|
|
91
107
|
}
|
|
92
108
|
|
|
93
109
|
class RoleBasedAccessPlugin {
|
|
@@ -121,18 +137,26 @@ classDiagram
|
|
|
121
137
|
|
|
122
138
|
### Base Plugin Class
|
|
123
139
|
|
|
124
|
-
The `Plugin` class provides
|
|
140
|
+
The `Plugin` class provides lifecycle hooks and method handling:
|
|
125
141
|
|
|
126
142
|
```typescript
|
|
127
143
|
export class Plugin {
|
|
128
144
|
// Called once during contract deployment, before contract's onDeployment
|
|
129
145
|
public onDeployment(_calldata: Calldata): void {}
|
|
130
146
|
|
|
147
|
+
// Called when contract bytecode is updated via updateContractFromExisting
|
|
148
|
+
public onUpdate(_calldata: Calldata): void {}
|
|
149
|
+
|
|
131
150
|
// Called before each method execution
|
|
132
151
|
public onExecutionStarted(_selector: Selector, _calldata: Calldata): void {}
|
|
133
152
|
|
|
134
153
|
// Called after each successful method execution
|
|
135
154
|
public onExecutionCompleted(_selector: Selector, _calldata: Calldata): void {}
|
|
155
|
+
|
|
156
|
+
// Handle method selectors - return BytesWriter if handled, null if not
|
|
157
|
+
public execute(_method: Selector, _calldata: Calldata): BytesWriter | null {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
136
160
|
}
|
|
137
161
|
```
|
|
138
162
|
|
|
@@ -144,14 +168,21 @@ import {
|
|
|
144
168
|
Calldata,
|
|
145
169
|
Selector,
|
|
146
170
|
Blockchain,
|
|
147
|
-
|
|
171
|
+
BytesWriter,
|
|
172
|
+
Revert,
|
|
173
|
+
encodeSelector
|
|
148
174
|
} from '@btc-vision/btc-runtime/runtime';
|
|
149
175
|
|
|
176
|
+
// Simple logging plugin using lifecycle hooks
|
|
150
177
|
class LoggingPlugin extends Plugin {
|
|
151
178
|
public override onDeployment(calldata: Calldata): void {
|
|
152
179
|
// Log deployment
|
|
153
180
|
}
|
|
154
181
|
|
|
182
|
+
public override onUpdate(calldata: Calldata): void {
|
|
183
|
+
// Handle contract upgrades
|
|
184
|
+
}
|
|
185
|
+
|
|
155
186
|
public override onExecutionStarted(selector: Selector, calldata: Calldata): void {
|
|
156
187
|
// Log method call before execution
|
|
157
188
|
}
|
|
@@ -160,6 +191,23 @@ class LoggingPlugin extends Plugin {
|
|
|
160
191
|
// Log method completion
|
|
161
192
|
}
|
|
162
193
|
}
|
|
194
|
+
|
|
195
|
+
// Plugin that handles method selectors (like UpgradeablePlugin)
|
|
196
|
+
class MethodHandlerPlugin extends Plugin {
|
|
197
|
+
public override execute(method: Selector, calldata: Calldata): BytesWriter | null {
|
|
198
|
+
switch (method) {
|
|
199
|
+
case encodeSelector('myPluginMethod()'):
|
|
200
|
+
return this.myPluginMethod();
|
|
201
|
+
default:
|
|
202
|
+
return null; // Not handled by this plugin
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private myPluginMethod(): BytesWriter {
|
|
207
|
+
// Method implementation
|
|
208
|
+
return new BytesWriter(0);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
163
211
|
```
|
|
164
212
|
|
|
165
213
|
## Registration
|
|
@@ -178,7 +226,7 @@ export class MyContract extends OP_NET {
|
|
|
178
226
|
|
|
179
227
|
// Create and register plugins
|
|
180
228
|
this.loggingPlugin = new LoggingPlugin();
|
|
181
|
-
|
|
229
|
+
this.registerPlugin(this.loggingPlugin);
|
|
182
230
|
}
|
|
183
231
|
}
|
|
184
232
|
```
|
|
@@ -190,7 +238,7 @@ public override onDeployment(calldata: Calldata): void {
|
|
|
190
238
|
const enableLogging = calldata.readBoolean();
|
|
191
239
|
|
|
192
240
|
if (enableLogging) {
|
|
193
|
-
|
|
241
|
+
this.registerPlugin(new LoggingPlugin());
|
|
194
242
|
}
|
|
195
243
|
|
|
196
244
|
// Continue with normal deployment
|
|
@@ -518,7 +566,7 @@ export class MyContract extends OP_NET {
|
|
|
518
566
|
Blockchain.nextPointer,
|
|
519
567
|
Blockchain.nextPointer
|
|
520
568
|
);
|
|
521
|
-
|
|
569
|
+
this.registerPlugin(this.feePlugin);
|
|
522
570
|
}
|
|
523
571
|
|
|
524
572
|
@method(
|
|
@@ -551,15 +599,21 @@ export class MyContract extends OP_NET {
|
|
|
551
599
|
### Execution Order
|
|
552
600
|
|
|
553
601
|
```
|
|
602
|
+
Deployment:
|
|
554
603
|
1. Plugin.onDeployment (for each registered plugin, in order)
|
|
555
604
|
2. Contract.onDeployment
|
|
556
605
|
|
|
557
|
-
|
|
606
|
+
Upgrade (when bytecode is updated):
|
|
607
|
+
1. Plugin.onUpdate (for each registered plugin, in order)
|
|
608
|
+
2. Contract.onUpdate
|
|
609
|
+
|
|
610
|
+
Method execution:
|
|
558
611
|
1. Plugin.onExecutionStarted (for each registered plugin, in order)
|
|
559
612
|
2. Contract.onExecutionStarted
|
|
560
|
-
3.
|
|
561
|
-
4.
|
|
562
|
-
5.
|
|
613
|
+
3. Plugin.execute (check plugins first, return if handled)
|
|
614
|
+
4. Contract method executes (if no plugin handled it)
|
|
615
|
+
5. Plugin.onExecutionCompleted (for each registered plugin, in order)
|
|
616
|
+
6. Contract.onExecutionCompleted
|
|
563
617
|
```
|
|
564
618
|
|
|
565
619
|
### Metrics Plugin Example
|
|
@@ -733,7 +787,7 @@ export class MyContract extends OP_NET {
|
|
|
733
787
|
super();
|
|
734
788
|
// Single plugin handles ALL access control
|
|
735
789
|
this.accessPlugin = new RoleBasedAccessPlugin(Blockchain.nextPointer);
|
|
736
|
-
|
|
790
|
+
this.registerPlugin(this.accessPlugin);
|
|
737
791
|
}
|
|
738
792
|
|
|
739
793
|
@method()
|
|
@@ -812,7 +866,7 @@ export class MyContract extends OP_NET {
|
|
|
812
866
|
public constructor() {
|
|
813
867
|
super();
|
|
814
868
|
this.pausablePlugin = new PausablePlugin(Blockchain.nextPointer);
|
|
815
|
-
|
|
869
|
+
this.registerPlugin(this.pausablePlugin);
|
|
816
870
|
}
|
|
817
871
|
|
|
818
872
|
@method(ABIDataTypes.UINT256)
|
|
@@ -861,19 +915,19 @@ export class CompleteContract extends OP_NET {
|
|
|
861
915
|
|
|
862
916
|
// Order matters - security checks first!
|
|
863
917
|
this.accessPlugin = new RoleBasedAccessPlugin(Blockchain.nextPointer);
|
|
864
|
-
|
|
918
|
+
this.registerPlugin(this.accessPlugin); // 1. Check permissions
|
|
865
919
|
|
|
866
920
|
this.pausablePlugin = new PausablePlugin(Blockchain.nextPointer);
|
|
867
|
-
|
|
921
|
+
this.registerPlugin(this.pausablePlugin); // 2. Check pause status
|
|
868
922
|
|
|
869
923
|
this.feePlugin = new FeeCollectorPlugin(
|
|
870
924
|
Blockchain.nextPointer,
|
|
871
925
|
Blockchain.nextPointer
|
|
872
926
|
);
|
|
873
|
-
|
|
927
|
+
this.registerPlugin(this.feePlugin); // 3. Fee calculations
|
|
874
928
|
|
|
875
929
|
this.metricsPlugin = new MetricsPlugin(Blockchain.nextPointer);
|
|
876
|
-
|
|
930
|
+
this.registerPlugin(this.metricsPlugin); // 4. Track metrics
|
|
877
931
|
}
|
|
878
932
|
|
|
879
933
|
// All plugins execute their hooks automatically
|
|
@@ -886,7 +940,9 @@ export class CompleteContract extends OP_NET {
|
|
|
886
940
|
| Lifecycle Event | Solidity | OPNet |
|
|
887
941
|
|-----------------|----------|-------|
|
|
888
942
|
| Contract deployment | Single constructor | `onDeployment` per plugin + contract |
|
|
943
|
+
| Contract upgrade | Proxy reinitialize | `onUpdate` per plugin + contract |
|
|
889
944
|
| Before method call | Modifiers (manual per function) | `onExecutionStarted` (automatic) |
|
|
945
|
+
| Method handling | Function dispatch | `execute` returns BytesWriter or null |
|
|
890
946
|
| After method call | No built-in hook | `onExecutionCompleted` (automatic) |
|
|
891
947
|
| Error handling | try/catch (limited) | Revert in any hook |
|
|
892
948
|
|
|
@@ -928,6 +984,10 @@ class MyPlugin extends Plugin {
|
|
|
928
984
|
// Implementation
|
|
929
985
|
}
|
|
930
986
|
|
|
987
|
+
public override onUpdate(calldata: Calldata): void {
|
|
988
|
+
// Implementation
|
|
989
|
+
}
|
|
990
|
+
|
|
931
991
|
public override onExecutionStarted(selector: Selector, calldata: Calldata): void {
|
|
932
992
|
// Implementation
|
|
933
993
|
}
|
|
@@ -935,6 +995,11 @@ class MyPlugin extends Plugin {
|
|
|
935
995
|
public override onExecutionCompleted(selector: Selector, calldata: Calldata): void {
|
|
936
996
|
// Implementation
|
|
937
997
|
}
|
|
998
|
+
|
|
999
|
+
public override execute(method: Selector, calldata: Calldata): BytesWriter | null {
|
|
1000
|
+
// Return BytesWriter if handled, null if not
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
938
1003
|
}
|
|
939
1004
|
```
|
|
940
1005
|
|
|
@@ -994,9 +1059,9 @@ public override onExecutionStarted(selector: Selector, calldata: Calldata): void
|
|
|
994
1059
|
|
|
995
1060
|
```typescript
|
|
996
1061
|
// Security checks should come first
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1062
|
+
this.registerPlugin(this.accessControl); // Check permissions first
|
|
1063
|
+
this.registerPlugin(this.pausable); // Then pausable
|
|
1064
|
+
this.registerPlugin(this.metrics); // Metrics last
|
|
1000
1065
|
```
|
|
1001
1066
|
|
|
1002
1067
|
---
|
|
@@ -432,6 +432,34 @@ const newContract = Blockchain.deployContractFromExisting(
|
|
|
432
432
|
);
|
|
433
433
|
```
|
|
434
434
|
|
|
435
|
+
### updateContractFromExisting
|
|
436
|
+
|
|
437
|
+
Updates the calling contract's bytecode from another deployed contract. The new bytecode takes effect at the next block.
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
updateContractFromExisting(
|
|
441
|
+
sourceAddress: Address,
|
|
442
|
+
calldata?: BytesWriter | null
|
|
443
|
+
): void
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
| Parameter | Type | Description |
|
|
447
|
+
|-----------|------|-------------|
|
|
448
|
+
| `sourceAddress` | `Address` | Contract containing new bytecode |
|
|
449
|
+
| `calldata` | `BytesWriter \| null` | Optional calldata passed to VM (default: empty) |
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
// Basic upgrade (not recommended without access control)
|
|
453
|
+
Blockchain.updateContractFromExisting(newBytecodeAddress);
|
|
454
|
+
|
|
455
|
+
// With calldata
|
|
456
|
+
const upgradeData = new BytesWriter(32);
|
|
457
|
+
upgradeData.writeU256(migrationVersion);
|
|
458
|
+
Blockchain.updateContractFromExisting(newBytecodeAddress, upgradeData);
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
> **Warning:** This is a privileged operation. Always implement access control (e.g., `onlyDeployer`) and consider using the `Upgradeable` base class or `UpgradeablePlugin` for timelock protection. See [Contract Upgrades](../advanced/contract-upgrades.md) for details.
|
|
462
|
+
|
|
435
463
|
## Cryptographic Operations
|
|
436
464
|
|
|
437
465
|
### sha256
|
|
@@ -698,20 +726,6 @@ log(data: string): void
|
|
|
698
726
|
Blockchain.log('Debug: operation started');
|
|
699
727
|
```
|
|
700
728
|
|
|
701
|
-
## Plugin Methods
|
|
702
|
-
|
|
703
|
-
### registerPlugin
|
|
704
|
-
|
|
705
|
-
Registers a plugin.
|
|
706
|
-
|
|
707
|
-
```typescript
|
|
708
|
-
registerPlugin(plugin: Plugin): void
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
```typescript
|
|
712
|
-
Blockchain.registerPlugin(new MyPlugin());
|
|
713
|
-
```
|
|
714
|
-
|
|
715
729
|
## Lifecycle Hooks
|
|
716
730
|
|
|
717
731
|
These are called by the runtime:
|
|
@@ -719,9 +733,29 @@ These are called by the runtime:
|
|
|
719
733
|
| Method | When Called |
|
|
720
734
|
|--------|-------------|
|
|
721
735
|
| `onDeployment(calldata)` | Contract deployment |
|
|
736
|
+
| `onUpdate(calldata)` | Contract bytecode update (via `updateContractFromExisting`) |
|
|
722
737
|
| `onExecutionStarted(selector, calldata)` | Before method execution |
|
|
723
738
|
| `onExecutionCompleted(selector, calldata)` | After successful execution |
|
|
724
739
|
|
|
740
|
+
### onUpdate
|
|
741
|
+
|
|
742
|
+
Called when the contract's bytecode is updated via `updateContractFromExisting`. Use this hook to perform storage migrations or initialization logic when upgrading.
|
|
743
|
+
|
|
744
|
+
```typescript
|
|
745
|
+
public override onUpdate(calldata: Calldata): void {
|
|
746
|
+
super.onUpdate(calldata); // Call plugins first
|
|
747
|
+
|
|
748
|
+
// Perform migration logic
|
|
749
|
+
const migrationVersion = calldata.readU64();
|
|
750
|
+
if (migrationVersion === 2) {
|
|
751
|
+
// Migrate from v1 to v2
|
|
752
|
+
this.migrateToV2();
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
> **Note:** The calldata is the same data passed to `Blockchain.updateContractFromExisting(sourceAddress, calldata)`. If no calldata was provided, an empty reader is passed.
|
|
758
|
+
|
|
725
759
|
---
|
|
726
760
|
|
|
727
761
|
**Navigation:**
|
|
@@ -62,6 +62,7 @@ classDiagram
|
|
|
62
62
|
+address Address (getter)
|
|
63
63
|
+contractDeployer Address (getter)
|
|
64
64
|
+onDeployment(_calldata: Calldata) void
|
|
65
|
+
+onUpdate(_calldata: Calldata) void
|
|
65
66
|
+onExecutionStarted(_selector: Selector, _calldata: Calldata) void
|
|
66
67
|
+onExecutionCompleted(_selector: Selector, _calldata: Calldata) void
|
|
67
68
|
+execute(method: Selector, _calldata: Calldata) BytesWriter
|
|
@@ -235,6 +236,27 @@ constructor(uint256 initialSupply, string memory tokenName) {
|
|
|
235
236
|
// Constructor runs every call, onDeployment runs once
|
|
236
237
|
```
|
|
237
238
|
|
|
239
|
+
### 2b. Update (onUpdate)
|
|
240
|
+
|
|
241
|
+
Runs when the contract's bytecode is updated via `updateContractFromExisting`:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
public override onUpdate(calldata: Calldata): void {
|
|
245
|
+
super.onUpdate(calldata); // Notify plugins
|
|
246
|
+
|
|
247
|
+
// Read migration parameters
|
|
248
|
+
const fromVersion = calldata.readU64();
|
|
249
|
+
|
|
250
|
+
// Perform migration based on version
|
|
251
|
+
if (fromVersion === 1) {
|
|
252
|
+
// Initialize new storage added in this version
|
|
253
|
+
this._newFeature.value = u256.fromU64(100);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
> **Note:** This hook is called on the **new** bytecode, not the old one. See [Contract Upgrades](../advanced/contract-upgrades.md#the-onupdate-lifecycle-hook) for details.
|
|
259
|
+
|
|
238
260
|
### 3. Method Execution
|
|
239
261
|
|
|
240
262
|
Methods are automatically routed via `@method` decorators:
|