@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
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# Upgradeable
|
|
2
|
+
|
|
3
|
+
The `Upgradeable` base class provides a secure upgrade mechanism with configurable timelock protection. Contracts extending `Upgradeable` can replace their bytecode while giving users time to assess pending changes.
|
|
4
|
+
|
|
5
|
+
> **Alternative**: If you're already extending another base class (like `OP20` or `OP721`), use the [`UpgradeablePlugin`](../advanced/contract-upgrades.md#using-the-upgradeableplugin) instead. Just call `this.registerPlugin(new UpgradeablePlugin(144))` in your constructor - no other code changes needed!
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import {
|
|
11
|
+
Upgradeable,
|
|
12
|
+
Calldata,
|
|
13
|
+
BytesWriter,
|
|
14
|
+
encodeSelector,
|
|
15
|
+
Selector,
|
|
16
|
+
ADDRESS_BYTE_LENGTH,
|
|
17
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
18
|
+
|
|
19
|
+
@final
|
|
20
|
+
export class MyContract extends Upgradeable {
|
|
21
|
+
// Set delay: 144 blocks = ~24 hours
|
|
22
|
+
protected readonly upgradeDelay: u64 = 144;
|
|
23
|
+
|
|
24
|
+
public override execute(method: Selector, calldata: Calldata): BytesWriter {
|
|
25
|
+
switch (method) {
|
|
26
|
+
case encodeSelector('submitUpgrade'):
|
|
27
|
+
return this.submitUpgrade(calldata.readAddress());
|
|
28
|
+
case encodeSelector('applyUpgrade'): {
|
|
29
|
+
const sourceAddress = calldata.readAddress();
|
|
30
|
+
const remainingLength = calldata.byteLength - ADDRESS_BYTE_LENGTH;
|
|
31
|
+
const updateCalldata = new BytesWriter(remainingLength);
|
|
32
|
+
if (remainingLength > 0) {
|
|
33
|
+
updateCalldata.writeBytes(calldata.readBytes(remainingLength));
|
|
34
|
+
}
|
|
35
|
+
return this.applyUpgrade(sourceAddress, updateCalldata);
|
|
36
|
+
}
|
|
37
|
+
case encodeSelector('cancelUpgrade'):
|
|
38
|
+
return this.cancelUpgrade();
|
|
39
|
+
default:
|
|
40
|
+
return super.execute(method, calldata);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Class Reference
|
|
47
|
+
|
|
48
|
+
### Properties
|
|
49
|
+
|
|
50
|
+
| Property | Type | Description |
|
|
51
|
+
|----------|------|-------------|
|
|
52
|
+
| `upgradeDelay` | `u64` | Blocks to wait before upgrade can be applied (default: 144 = ~24h) |
|
|
53
|
+
| `pendingUpgradeAddress` | `Address` | Source address of pending upgrade (zero if none) |
|
|
54
|
+
| `pendingUpgradeBlock` | `u64` | Block when upgrade was submitted (0 if none) |
|
|
55
|
+
| `upgradeEffectiveBlock` | `u64` | Block when upgrade can be applied (0 if none) |
|
|
56
|
+
| `hasPendingUpgrade` | `bool` | Whether an upgrade is pending |
|
|
57
|
+
| `canApplyUpgrade` | `bool` | Whether the delay has elapsed |
|
|
58
|
+
|
|
59
|
+
### Methods
|
|
60
|
+
|
|
61
|
+
#### submitUpgrade
|
|
62
|
+
|
|
63
|
+
Submits an upgrade for timelock. Only callable by deployer.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
protected submitUpgrade(sourceAddress: Address): BytesWriter
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Parameters:**
|
|
70
|
+
- `sourceAddress`: The address of the contract containing the new bytecode
|
|
71
|
+
|
|
72
|
+
**Reverts if:**
|
|
73
|
+
- Caller is not the deployer
|
|
74
|
+
- Source is not a deployed contract
|
|
75
|
+
- An upgrade is already pending
|
|
76
|
+
|
|
77
|
+
**Emits:** `UpgradeSubmitted(sourceAddress, submitBlock, effectiveBlock)`
|
|
78
|
+
|
|
79
|
+
#### applyUpgrade
|
|
80
|
+
|
|
81
|
+
Applies a pending upgrade after the delay has passed. Only callable by deployer.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
protected applyUpgrade(sourceAddress: Address, calldata: BytesWriter): BytesWriter
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Parameters:**
|
|
88
|
+
- `sourceAddress`: The source contract address (must match pending)
|
|
89
|
+
- `calldata`: Data passed to the `onUpdate` hook of the new contract
|
|
90
|
+
|
|
91
|
+
**Reverts if:**
|
|
92
|
+
- Caller is not the deployer
|
|
93
|
+
- No upgrade is pending
|
|
94
|
+
- Delay has not elapsed
|
|
95
|
+
- Address does not match pending upgrade
|
|
96
|
+
|
|
97
|
+
**Emits:** `UpgradeApplied(sourceAddress, appliedAtBlock)`
|
|
98
|
+
|
|
99
|
+
#### cancelUpgrade
|
|
100
|
+
|
|
101
|
+
Cancels a pending upgrade. Only callable by deployer.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
protected cancelUpgrade(): BytesWriter
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Reverts if:**
|
|
108
|
+
- Caller is not the deployer
|
|
109
|
+
- No upgrade is pending
|
|
110
|
+
|
|
111
|
+
**Emits:** `UpgradeCancelled(sourceAddress, cancelledAtBlock)`
|
|
112
|
+
|
|
113
|
+
## Events
|
|
114
|
+
|
|
115
|
+
### UpgradeSubmittedEvent
|
|
116
|
+
|
|
117
|
+
Emitted when an upgrade is submitted.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
class UpgradeSubmittedEvent extends NetEvent {
|
|
121
|
+
constructor(
|
|
122
|
+
sourceAddress: Address, // Contract with new bytecode
|
|
123
|
+
submitBlock: u64, // Block when submitted
|
|
124
|
+
effectiveBlock: u64 // Block when can be applied
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### UpgradeAppliedEvent
|
|
130
|
+
|
|
131
|
+
Emitted when an upgrade is applied.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
class UpgradeAppliedEvent extends NetEvent {
|
|
135
|
+
constructor(
|
|
136
|
+
sourceAddress: Address, // Contract with new bytecode
|
|
137
|
+
appliedAtBlock: u64 // Block when applied
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### UpgradeCancelledEvent
|
|
143
|
+
|
|
144
|
+
Emitted when a pending upgrade is cancelled.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
class UpgradeCancelledEvent extends NetEvent {
|
|
148
|
+
constructor(
|
|
149
|
+
sourceAddress: Address, // Cancelled source contract
|
|
150
|
+
cancelledAtBlock: u64 // Block when cancelled
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Usage Patterns
|
|
156
|
+
|
|
157
|
+
### Basic Upgradeable Contract
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
@final
|
|
161
|
+
export class SimpleUpgradeable extends Upgradeable {
|
|
162
|
+
protected readonly upgradeDelay: u64 = 144; // ~1 day
|
|
163
|
+
|
|
164
|
+
public override execute(method: Selector, calldata: Calldata): BytesWriter {
|
|
165
|
+
switch (method) {
|
|
166
|
+
case encodeSelector('submitUpgrade'):
|
|
167
|
+
return this.submitUpgrade(calldata.readAddress());
|
|
168
|
+
case encodeSelector('applyUpgrade'): {
|
|
169
|
+
const sourceAddress = calldata.readAddress();
|
|
170
|
+
const remainingLength = calldata.byteLength - ADDRESS_BYTE_LENGTH;
|
|
171
|
+
const updateCalldata = new BytesWriter(remainingLength);
|
|
172
|
+
if (remainingLength > 0) {
|
|
173
|
+
updateCalldata.writeBytes(calldata.readBytes(remainingLength));
|
|
174
|
+
}
|
|
175
|
+
return this.applyUpgrade(sourceAddress, updateCalldata);
|
|
176
|
+
}
|
|
177
|
+
case encodeSelector('cancelUpgrade'):
|
|
178
|
+
return this.cancelUpgrade();
|
|
179
|
+
default:
|
|
180
|
+
return super.execute(method, calldata);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### With Upgrade Status Views
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
@final
|
|
190
|
+
export class UpgradeableWithViews extends Upgradeable {
|
|
191
|
+
protected readonly upgradeDelay: u64 = 1008; // ~1 week
|
|
192
|
+
|
|
193
|
+
public override execute(method: Selector, calldata: Calldata): BytesWriter {
|
|
194
|
+
switch (method) {
|
|
195
|
+
// Upgrade actions
|
|
196
|
+
case encodeSelector('submitUpgrade'):
|
|
197
|
+
return this.submitUpgrade(calldata.readAddress());
|
|
198
|
+
case encodeSelector('applyUpgrade'): {
|
|
199
|
+
const sourceAddress = calldata.readAddress();
|
|
200
|
+
const remainingLength = calldata.byteLength - ADDRESS_BYTE_LENGTH;
|
|
201
|
+
const updateCalldata = new BytesWriter(remainingLength);
|
|
202
|
+
if (remainingLength > 0) {
|
|
203
|
+
updateCalldata.writeBytes(calldata.readBytes(remainingLength));
|
|
204
|
+
}
|
|
205
|
+
return this.applyUpgrade(sourceAddress, updateCalldata);
|
|
206
|
+
}
|
|
207
|
+
case encodeSelector('cancelUpgrade'):
|
|
208
|
+
return this.cancelUpgrade();
|
|
209
|
+
|
|
210
|
+
// View methods
|
|
211
|
+
case encodeSelector('getPendingUpgrade'):
|
|
212
|
+
return this.getPendingUpgrade();
|
|
213
|
+
case encodeSelector('getUpgradeStatus'):
|
|
214
|
+
return this.getUpgradeStatus();
|
|
215
|
+
|
|
216
|
+
default:
|
|
217
|
+
return super.execute(method, calldata);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private getPendingUpgrade(): BytesWriter {
|
|
222
|
+
const response = new BytesWriter(32);
|
|
223
|
+
response.writeAddress(this.pendingUpgradeAddress);
|
|
224
|
+
return response;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private getUpgradeStatus(): BytesWriter {
|
|
228
|
+
const response = new BytesWriter(17);
|
|
229
|
+
response.writeBoolean(this.hasPendingUpgrade);
|
|
230
|
+
response.writeU64(this.pendingUpgradeBlock);
|
|
231
|
+
response.writeU64(this.upgradeEffectiveBlock);
|
|
232
|
+
return response;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Emergency Upgrades (Not Recommended)
|
|
238
|
+
|
|
239
|
+
For contracts that need faster upgrades (use with caution):
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
@final
|
|
243
|
+
export class QuickUpgradeable extends Upgradeable {
|
|
244
|
+
// Only 6 blocks (~1 hour) - use only for emergencies
|
|
245
|
+
protected readonly upgradeDelay: u64 = 6;
|
|
246
|
+
|
|
247
|
+
// ... rest of implementation
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Security Considerations
|
|
252
|
+
|
|
253
|
+
### 1. Delay Selection
|
|
254
|
+
|
|
255
|
+
Choose an appropriate delay based on your contract's risk profile:
|
|
256
|
+
|
|
257
|
+
| Contract Type | Recommended Delay |
|
|
258
|
+
|---------------|-------------------|
|
|
259
|
+
| Test/Development | 1-6 blocks |
|
|
260
|
+
| Standard DeFi | 144 blocks (~24 hours) |
|
|
261
|
+
| High-value vaults | 1008 blocks (~1 week) |
|
|
262
|
+
| Governance contracts | 4320 blocks (~1 month) |
|
|
263
|
+
|
|
264
|
+
### 2. Source Validation
|
|
265
|
+
|
|
266
|
+
The `submitUpgrade` function validates that the source is a deployed contract. This prevents:
|
|
267
|
+
- Submitting non-existent addresses
|
|
268
|
+
- Last-minute malicious deployments
|
|
269
|
+
|
|
270
|
+
### 3. Address Match Verification
|
|
271
|
+
|
|
272
|
+
The `applyUpgrade` function requires the address to match the pending upgrade. This prevents:
|
|
273
|
+
- Front-running attacks
|
|
274
|
+
- Address substitution attacks
|
|
275
|
+
|
|
276
|
+
### 4. Storage Compatibility
|
|
277
|
+
|
|
278
|
+
When upgrading, ensure storage layout compatibility:
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
// V1 - Original
|
|
282
|
+
class ContractV1 extends Upgradeable {
|
|
283
|
+
private ptr1: u16 = Blockchain.nextPointer; // Pointer 1
|
|
284
|
+
private ptr2: u16 = Blockchain.nextPointer; // Pointer 2
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// V2 - Add new pointers at the END
|
|
288
|
+
class ContractV2 extends Upgradeable {
|
|
289
|
+
private ptr1: u16 = Blockchain.nextPointer; // Pointer 1 (same)
|
|
290
|
+
private ptr2: u16 = Blockchain.nextPointer; // Pointer 2 (same)
|
|
291
|
+
private ptr3: u16 = Blockchain.nextPointer; // Pointer 3 (NEW)
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Upgrade Workflow
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
1. Deploy new bytecode contract
|
|
299
|
+
└─> Returns: newContractAddress
|
|
300
|
+
|
|
301
|
+
2. Submit upgrade
|
|
302
|
+
└─> submitUpgrade(newContractAddress)
|
|
303
|
+
└─> Emits: UpgradeSubmitted
|
|
304
|
+
|
|
305
|
+
3. Wait for delay
|
|
306
|
+
└─> Users can monitor and exit
|
|
307
|
+
|
|
308
|
+
4. Apply upgrade
|
|
309
|
+
└─> applyUpgrade(newContractAddress)
|
|
310
|
+
└─> Emits: UpgradeApplied
|
|
311
|
+
└─> VM calls onUpdate() on new bytecode
|
|
312
|
+
└─> New bytecode active next block
|
|
313
|
+
|
|
314
|
+
5. (Optional) Discard source contract
|
|
315
|
+
└─> Source contract can be abandoned
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### The onUpdate Hook
|
|
319
|
+
|
|
320
|
+
Override `onUpdate` in your new contract version to perform migrations:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
@final
|
|
324
|
+
export class MyContractV2 extends Upgradeable {
|
|
325
|
+
protected readonly upgradeDelay: u64 = 144;
|
|
326
|
+
|
|
327
|
+
// New storage added in V2
|
|
328
|
+
private newFeaturePointer: u16 = Blockchain.nextPointer;
|
|
329
|
+
private _newFeature: StoredU256;
|
|
330
|
+
|
|
331
|
+
public constructor() {
|
|
332
|
+
super();
|
|
333
|
+
this._newFeature = new StoredU256(this.newFeaturePointer, EMPTY_POINTER);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
public override onUpdate(calldata: Calldata): void {
|
|
337
|
+
super.onUpdate(calldata);
|
|
338
|
+
// Initialize new storage
|
|
339
|
+
this._newFeature.value = u256.fromU64(100);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ... execute() and other methods
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
See [The onUpdate Lifecycle Hook](../advanced/contract-upgrades.md#the-onupdate-lifecycle-hook) for more details.
|
|
347
|
+
|
|
348
|
+
## Combining with Other Base Classes
|
|
349
|
+
|
|
350
|
+
### Upgradeable + ReentrancyGuard
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// Create a combined base class
|
|
354
|
+
class UpgradeableWithReentrancy extends Upgradeable {
|
|
355
|
+
protected readonly reentrancyLevel: ReentrancyLevel = ReentrancyLevel.STANDARD;
|
|
356
|
+
private _locked: StoredBoolean;
|
|
357
|
+
|
|
358
|
+
protected constructor() {
|
|
359
|
+
super();
|
|
360
|
+
this._locked = new StoredBoolean(Blockchain.nextPointer, false);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
protected nonReentrant(): void {
|
|
364
|
+
if (this._locked.value) {
|
|
365
|
+
throw new Revert('ReentrancyGuard: LOCKED');
|
|
366
|
+
}
|
|
367
|
+
this._locked.value = true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
protected releaseGuard(): void {
|
|
371
|
+
this._locked.value = false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
@final
|
|
376
|
+
export class SecureUpgradeableVault extends UpgradeableWithReentrancy {
|
|
377
|
+
// Implementation with both upgrade and reentrancy protection
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Solidity Comparison
|
|
382
|
+
|
|
383
|
+
| Feature | OpenZeppelin UUPS | OPNet Upgradeable |
|
|
384
|
+
|---------|-------------------|-------------------|
|
|
385
|
+
| Upgrade mechanism | delegatecall | Native bytecode replacement |
|
|
386
|
+
| Storage location | Implementation contract | Same contract |
|
|
387
|
+
| Proxy overhead | Yes | No |
|
|
388
|
+
| Timelock | Separate contract (optional) | Built-in |
|
|
389
|
+
| Events | Custom | Built-in |
|
|
390
|
+
| Cancel upgrade | Custom implementation | Built-in |
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
**Navigation:**
|
|
395
|
+
- Previous: [ReentrancyGuard](./reentrancy-guard.md)
|
|
396
|
+
- Next: [Address Type](../types/address.md)
|
|
@@ -587,7 +587,6 @@ classDiagram
|
|
|
587
587
|
+getBlockHash(blockNumber) Uint8Array
|
|
588
588
|
+emit(event) void
|
|
589
589
|
+log(data) void
|
|
590
|
-
+registerPlugin(plugin) void
|
|
591
590
|
}
|
|
592
591
|
|
|
593
592
|
class Block {
|
|
@@ -715,7 +714,6 @@ export class MyContract extends OP_NET {
|
|
|
715
714
|
| `Blockchain.getBlockHash()` | `Uint8Array` | Historical block hash |
|
|
716
715
|
| `Blockchain.emit()` | `void` | Emit event |
|
|
717
716
|
| `Blockchain.log()` | `void` | Debug logging (testing only) |
|
|
718
|
-
| `Blockchain.registerPlugin()` | `void` | Register a plugin |
|
|
719
717
|
|
|
720
718
|
---
|
|
721
719
|
|
|
@@ -31,6 +31,7 @@ classDiagram
|
|
|
31
31
|
-Uint8Array typedArray
|
|
32
32
|
-u32 currentOffset
|
|
33
33
|
+BytesWriter(length)
|
|
34
|
+
+write~T~(value) void
|
|
34
35
|
+writeU8(value)
|
|
35
36
|
+writeU16(value, be)
|
|
36
37
|
+writeU32(value, be)
|
|
@@ -38,9 +39,13 @@ classDiagram
|
|
|
38
39
|
+writeU128(value, be)
|
|
39
40
|
+writeU256(value, be)
|
|
40
41
|
+writeAddress(addr)
|
|
42
|
+
+writeExtendedAddress(extAddr)
|
|
43
|
+
+writeSchnorrSignature(addr, sig)
|
|
41
44
|
+writeString(str)
|
|
42
45
|
+writeBytes(data)
|
|
43
46
|
+writeBoolean(bool)
|
|
47
|
+
+writeExtendedAddressArray(arr)
|
|
48
|
+
+writeExtendedAddressMapU256(map)
|
|
44
49
|
+getBuffer() Uint8Array
|
|
45
50
|
}
|
|
46
51
|
|
|
@@ -48,6 +53,7 @@ classDiagram
|
|
|
48
53
|
-DataView buffer
|
|
49
54
|
-i32 currentOffset
|
|
50
55
|
+BytesReader(bytes)
|
|
56
|
+
+read~T~() T
|
|
51
57
|
+readU8() u8
|
|
52
58
|
+readU16(be) u16
|
|
53
59
|
+readU32(be) u32
|
|
@@ -55,9 +61,13 @@ classDiagram
|
|
|
55
61
|
+readU128(be) u128
|
|
56
62
|
+readU256(be) u256
|
|
57
63
|
+readAddress() Address
|
|
64
|
+
+readExtendedAddress() ExtendedAddress
|
|
65
|
+
+readSchnorrSignature() SchnorrSignature
|
|
58
66
|
+readString() string
|
|
59
67
|
+readBytes(length) Uint8Array
|
|
60
68
|
+readBoolean() bool
|
|
69
|
+
+readExtendedAddressArray() ExtendedAddress[]
|
|
70
|
+
+readExtendedAddressMapU256() ExtendedAddressMap
|
|
61
71
|
+hasMoreData() bool
|
|
62
72
|
}
|
|
63
73
|
|
|
@@ -113,6 +123,12 @@ writer.writeI64(value);
|
|
|
113
123
|
// Address (32 bytes)
|
|
114
124
|
writer.writeAddress(address);
|
|
115
125
|
|
|
126
|
+
// ExtendedAddress (64 bytes: 32 tweaked key + 32 ML-DSA key hash)
|
|
127
|
+
writer.writeExtendedAddress(extendedAddress);
|
|
128
|
+
|
|
129
|
+
// Schnorr signature with signer address (128 bytes total)
|
|
130
|
+
writer.writeSchnorrSignature(signerAddress, signature);
|
|
131
|
+
|
|
116
132
|
// String without length prefix
|
|
117
133
|
writer.writeString('Hello, World!');
|
|
118
134
|
|
|
@@ -129,6 +145,25 @@ writer.writeBytesWithLength(data);
|
|
|
129
145
|
writer.writeSelector(selector);
|
|
130
146
|
```
|
|
131
147
|
|
|
148
|
+
### Generic Write Method
|
|
149
|
+
|
|
150
|
+
The `write<T>()` method automatically selects the correct write method based on the type:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// Automatically dispatches to the correct write method
|
|
154
|
+
writer.write<u64>(12345); // writeU64
|
|
155
|
+
writer.write<u256>(amount); // writeU256
|
|
156
|
+
writer.write<bool>(true); // writeBoolean
|
|
157
|
+
writer.write<Address>(addr); // writeAddress
|
|
158
|
+
writer.write<ExtendedAddress>(extAddr); // writeExtendedAddress
|
|
159
|
+
writer.write<string>('hello'); // writeStringWithLength
|
|
160
|
+
|
|
161
|
+
// Supported types:
|
|
162
|
+
// - Primitives: bool, u8, u16, u32, u64, i8, i16, i32, i64
|
|
163
|
+
// - Big numbers: u128, u256, i128
|
|
164
|
+
// - Complex: Address, ExtendedAddress, Selector, string, Uint8Array
|
|
165
|
+
```
|
|
166
|
+
|
|
132
167
|
### Writing Arrays
|
|
133
168
|
|
|
134
169
|
All array methods use a u16 length prefix (max 65535 elements):
|
|
@@ -137,6 +172,9 @@ All array methods use a u16 length prefix (max 65535 elements):
|
|
|
137
172
|
// Address array (u16 length prefix + addresses)
|
|
138
173
|
writer.writeAddressArray(addresses);
|
|
139
174
|
|
|
175
|
+
// ExtendedAddress array (u16 length prefix + 64-byte addresses)
|
|
176
|
+
writer.writeExtendedAddressArray(extendedAddresses);
|
|
177
|
+
|
|
140
178
|
// Numeric arrays (u16 length prefix + values)
|
|
141
179
|
writer.writeU8Array(u8Values);
|
|
142
180
|
writer.writeU16Array(u16Values);
|
|
@@ -150,6 +188,9 @@ writer.writeArrayOfBuffer(buffers);
|
|
|
150
188
|
|
|
151
189
|
// AddressMap<u256> (u16 count, then address + u256 pairs)
|
|
152
190
|
writer.writeAddressMapU256(addressMap);
|
|
191
|
+
|
|
192
|
+
// ExtendedAddressMap<u256> (u16 count, then 64-byte address + u256 pairs)
|
|
193
|
+
writer.writeExtendedAddressMapU256(extendedAddressMap);
|
|
153
194
|
```
|
|
154
195
|
|
|
155
196
|
### Getting Results
|
|
@@ -222,6 +263,14 @@ const i64val: i64 = reader.readI64();
|
|
|
222
263
|
// Address (32 bytes)
|
|
223
264
|
const addr: Address = reader.readAddress();
|
|
224
265
|
|
|
266
|
+
// ExtendedAddress (64 bytes: 32 tweaked key + 32 ML-DSA key hash)
|
|
267
|
+
const extAddr: ExtendedAddress = reader.readExtendedAddress();
|
|
268
|
+
|
|
269
|
+
// Schnorr signature with signer address (128 bytes)
|
|
270
|
+
const schnorrSig: SchnorrSignature = reader.readSchnorrSignature();
|
|
271
|
+
const signer: ExtendedAddress = schnorrSig.address;
|
|
272
|
+
const signature: Uint8Array = schnorrSig.signature;
|
|
273
|
+
|
|
225
274
|
// String with known length (bytes read, zeroStop = true)
|
|
226
275
|
const name: string = reader.readString(32); // reads up to 32 bytes, stops at null
|
|
227
276
|
|
|
@@ -238,6 +287,25 @@ const data2: Uint8Array = reader.readBytesWithLength();
|
|
|
238
287
|
const selector: Selector = reader.readSelector();
|
|
239
288
|
```
|
|
240
289
|
|
|
290
|
+
### Generic Read Method
|
|
291
|
+
|
|
292
|
+
The `read<T>()` method automatically selects the correct read method based on the type:
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
// Automatically dispatches to the correct read method
|
|
296
|
+
const num: u64 = reader.read<u64>(); // readU64
|
|
297
|
+
const amount: u256 = reader.read<u256>(); // readU256
|
|
298
|
+
const flag: bool = reader.read<bool>(); // readBoolean
|
|
299
|
+
const addr: Address = reader.read<Address>(); // readAddress
|
|
300
|
+
const extAddr: ExtendedAddress = reader.read<ExtendedAddress>(); // readExtendedAddress
|
|
301
|
+
const text: string = reader.read<string>(); // readStringWithLength
|
|
302
|
+
|
|
303
|
+
// Supported types:
|
|
304
|
+
// - Primitives: bool, u8, u16, u32, u64, i8, i16, i32, i64
|
|
305
|
+
// - Big numbers: u128, u256, i128
|
|
306
|
+
// - Complex: Address, ExtendedAddress, Selector, string
|
|
307
|
+
```
|
|
308
|
+
|
|
241
309
|
### Reading Arrays
|
|
242
310
|
|
|
243
311
|
All array methods expect a u16 length prefix:
|
|
@@ -246,6 +314,9 @@ All array methods expect a u16 length prefix:
|
|
|
246
314
|
// Address array
|
|
247
315
|
const addresses: Address[] = reader.readAddressArray();
|
|
248
316
|
|
|
317
|
+
// ExtendedAddress array (64 bytes each)
|
|
318
|
+
const extAddresses: ExtendedAddress[] = reader.readExtendedAddressArray();
|
|
319
|
+
|
|
249
320
|
// Numeric arrays
|
|
250
321
|
const u8Values: u8[] = reader.readU8Array();
|
|
251
322
|
const u16Values: u16[] = reader.readU16Array();
|
|
@@ -259,6 +330,9 @@ const buffers: Uint8Array[] = reader.readArrayOfBuffer();
|
|
|
259
330
|
|
|
260
331
|
// AddressMap<u256>
|
|
261
332
|
const addressMap: AddressMap<u256> = reader.readAddressMapU256();
|
|
333
|
+
|
|
334
|
+
// ExtendedAddressMap<u256> (64-byte addresses as keys)
|
|
335
|
+
const extAddressMap: ExtendedAddressMap<u256> = reader.readExtendedAddressMapU256();
|
|
262
336
|
```
|
|
263
337
|
|
|
264
338
|
### Position Management
|
|
@@ -291,6 +365,8 @@ reader.verifyEnd(offset + 32); // Throws Revert if not enough bytes
|
|
|
291
365
|
| `u128`/`i128` | 16 | Big-endian (default) |
|
|
292
366
|
| `u256` | 32 | Big-endian (default) |
|
|
293
367
|
| `Address` | 32 | Raw bytes |
|
|
368
|
+
| `ExtendedAddress` | 64 | 32 bytes tweaked key + 32 bytes ML-DSA key hash |
|
|
369
|
+
| `SchnorrSignature` | 128 | 64 bytes ExtendedAddress + 64 bytes signature |
|
|
294
370
|
| `Selector` | 4 | Big-endian (u32) |
|
|
295
371
|
| `string` | 4 + n | Length prefix (u32 BE) + UTF-8 |
|
|
296
372
|
| `bytes` | 4 + n | Length prefix (u32 BE) + raw |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btc-vision/btc-runtime",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0-alpha",
|
|
4
4
|
"description": "Bitcoin L1 Smart Contract Runtime for OPNet. Build decentralized applications on Bitcoin using AssemblyScript and WebAssembly. Fully audited.",
|
|
5
5
|
"main": "btc/index.ts",
|
|
6
6
|
"types": "btc/index.ts",
|
|
@@ -56,17 +56,17 @@
|
|
|
56
56
|
"docs"
|
|
57
57
|
],
|
|
58
58
|
"engines": {
|
|
59
|
-
"node": ">=
|
|
59
|
+
"node": ">=24.0.0"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@assemblyscript/loader": "^0.28.9",
|
|
63
63
|
"@btc-vision/as-bignum": "^0.0.7",
|
|
64
64
|
"@btc-vision/opnet-transform": "^0.2.1",
|
|
65
|
-
"@eslint/js": "9.39.
|
|
65
|
+
"@eslint/js": "9.39.2",
|
|
66
66
|
"gulplog": "^2.2.0",
|
|
67
67
|
"ts-node": "^10.9.2",
|
|
68
68
|
"typescript": "^5.9.3",
|
|
69
|
-
"typescript-eslint": "^8.
|
|
69
|
+
"typescript-eslint": "^8.53.1"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@btc-vision/as-covers-assembly": "^0.4.4",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@btc-vision/as-pect-assembly": "^8.2.0",
|
|
75
75
|
"@btc-vision/as-pect-cli": "^8.2.0",
|
|
76
76
|
"@btc-vision/as-pect-transform": "^8.2.0",
|
|
77
|
-
"@types/node": "^
|
|
77
|
+
"@types/node": "^25.0.9",
|
|
78
78
|
"assemblyscript": "^0.28.9"
|
|
79
79
|
}
|
|
80
80
|
}
|