@btc-vision/btc-runtime 1.11.0-rc.9 → 1.11.0
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 +7 -7
- package/docs/README.md +39 -39
- package/docs/advanced/bitcoin-scripts.md +17 -17
- package/docs/advanced/{contract-upgrades.md → contract-updates.md} +90 -98
- package/docs/advanced/cross-contract-calls.md +4 -4
- package/docs/advanced/plugins.md +21 -21
- package/docs/advanced/quantum-resistance.md +32 -32
- package/docs/advanced/signature-verification.md +22 -22
- package/docs/api-reference/blockchain.md +14 -14
- package/docs/api-reference/events.md +2 -2
- package/docs/api-reference/op20.md +7 -7
- package/docs/api-reference/op721.md +7 -7
- package/docs/api-reference/storage.md +2 -2
- package/docs/contracts/op-net-base.md +15 -15
- package/docs/contracts/op20-token.md +3 -3
- package/docs/contracts/op20s-signatures.md +2 -2
- package/docs/contracts/op721-nft.md +3 -3
- package/docs/contracts/reentrancy-guard.md +5 -7
- package/docs/contracts/updatable.md +384 -0
- package/docs/core-concepts/blockchain-environment.md +10 -10
- package/docs/core-concepts/decorators.md +5 -5
- package/docs/core-concepts/events.md +6 -6
- package/docs/core-concepts/pointers.md +5 -5
- package/docs/core-concepts/security.md +5 -5
- package/docs/core-concepts/storage-system.md +24 -24
- package/docs/examples/basic-token.md +8 -8
- package/docs/examples/nft-with-reservations.md +9 -9
- package/docs/examples/oracle-integration.md +13 -13
- package/docs/examples/stablecoin.md +10 -10
- package/docs/getting-started/first-contract.md +8 -8
- package/docs/getting-started/installation.md +2 -2
- package/docs/getting-started/project-structure.md +6 -6
- package/docs/storage/memory-maps.md +8 -8
- package/docs/storage/stored-arrays.md +6 -6
- package/docs/storage/stored-maps.md +8 -8
- package/docs/storage/stored-primitives.md +6 -6
- package/docs/types/address.md +13 -13
- package/docs/types/bytes-writer-reader.md +18 -18
- package/docs/types/calldata.md +12 -12
- package/package.json +10 -10
- package/runtime/constants/Exports.ts +0 -30
- package/runtime/contracts/OP20.ts +7 -7
- package/runtime/contracts/OP721.ts +60 -74
- package/runtime/contracts/OP_NET.ts +2 -2
- package/runtime/contracts/ReentrancyGuard.ts +1 -5
- package/runtime/contracts/Updatable.ts +241 -0
- package/runtime/contracts/interfaces/OP721InitParameters.ts +8 -8
- package/runtime/env/BlockchainEnvironment.ts +5 -5
- package/runtime/env/global.ts +7 -6
- package/runtime/events/predefined/{ApprovedEvent.ts → OP20ApprovedEvent.ts} +1 -1
- package/runtime/events/predefined/{BurnedEvent.ts → OP20BurnedEvent.ts} +1 -1
- package/runtime/events/predefined/{MintedEvent.ts → OP20MintedEvent.ts} +1 -1
- package/runtime/events/predefined/{TransferredEvent.ts → OP20TransferredEvent.ts} +1 -1
- package/runtime/events/predefined/OP721ApprovedEvent.ts +17 -0
- package/runtime/events/predefined/{ApprovedForAll.ts → OP721ApprovedForAllEvent.ts} +1 -1
- package/runtime/events/predefined/OP721BurnedEvent.ts +16 -0
- package/runtime/events/predefined/OP721MintedEvent.ts +16 -0
- package/runtime/events/predefined/OP721TransferredEvent.ts +18 -0
- package/runtime/events/predefined/index.ts +5 -5
- package/runtime/events/{upgradeable/UpgradeableEvents.ts → updatable/UpdatableEvents.ts} +9 -9
- package/runtime/hashing/keccak256.ts +1 -1
- package/runtime/index.ts +3 -5
- package/runtime/plugins/UpdatablePlugin.ts +276 -0
- package/runtime/script/Networks.ts +1 -1
- package/runtime/storage/StoredBoolean.ts +23 -12
- package/runtime/types/Address.ts +1 -1
- package/runtime/types/ExtendedAddress.ts +1 -1
- package/docs/contracts/upgradeable.md +0 -396
- package/runtime/contracts/Upgradeable.ts +0 -242
- package/runtime/contracts/interfaces/IOP1155.ts +0 -33
- package/runtime/contracts/interfaces/OP1155InitParameters.ts +0 -11
- package/runtime/plugins/UpgradeablePlugin.ts +0 -279
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OP_NET Base Contract
|
|
2
2
|
|
|
3
|
-
`OP_NET` is the base class for all
|
|
3
|
+
`OP_NET` is the base class for all OP_NET smart contracts. It implements the `IBTC` interface and provides the foundational structure for contract lifecycle, method dispatching, event emission, and access control.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -42,7 +42,7 @@ contract MyContract {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
//
|
|
45
|
+
// OP_NET: AUTOMATIC method routing via @method decorators
|
|
46
46
|
// - Constructor runs on EVERY call
|
|
47
47
|
// - Routing is automatic via decorator system
|
|
48
48
|
// - One-time init in onDeployment()
|
|
@@ -52,7 +52,7 @@ contract MyContract {
|
|
|
52
52
|
|
|
53
53
|
### Inheritance Hierarchy
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
OP_NET contracts follow a clear inheritance pattern:
|
|
56
56
|
|
|
57
57
|
```mermaid
|
|
58
58
|
classDiagram
|
|
@@ -118,7 +118,7 @@ classDiagram
|
|
|
118
118
|
|
|
119
119
|
### Deployment and Execution Flow
|
|
120
120
|
|
|
121
|
-
The following diagram shows how contracts are deployed and executed on
|
|
121
|
+
The following diagram shows how contracts are deployed and executed on OP_NET:
|
|
122
122
|
|
|
123
123
|
```mermaid
|
|
124
124
|
---
|
|
@@ -198,7 +198,7 @@ public constructor() {
|
|
|
198
198
|
|
|
199
199
|
**Key Difference from Solidity:**
|
|
200
200
|
|
|
201
|
-
| Solidity |
|
|
201
|
+
| Solidity | OP_NET |
|
|
202
202
|
|----------|-------|
|
|
203
203
|
| Constructor runs once at deployment | Constructor runs every call |
|
|
204
204
|
| Initialize state in constructor | Initialize state in `onDeployment` |
|
|
@@ -232,7 +232,7 @@ constructor(uint256 initialSupply, string memory tokenName) {
|
|
|
232
232
|
_mint(msg.sender, initialSupply);
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
//
|
|
235
|
+
// OP_NET: One-time init in onDeployment()
|
|
236
236
|
// Constructor runs every call, onDeployment runs once
|
|
237
237
|
```
|
|
238
238
|
|
|
@@ -255,7 +255,7 @@ public override onUpdate(calldata: Calldata): void {
|
|
|
255
255
|
}
|
|
256
256
|
```
|
|
257
257
|
|
|
258
|
-
> **Note:** This hook is called on the **new** bytecode, not the old one. See [Contract
|
|
258
|
+
> **Note:** This hook is called on the **new** bytecode, not the old one. See [Contract Updates](../advanced/updatable#the-onupdate-lifecycle-hook) for details.
|
|
259
259
|
|
|
260
260
|
### 3. Method Execution
|
|
261
261
|
|
|
@@ -397,12 +397,12 @@ public transfer(calldata: Calldata): BytesWriter {
|
|
|
397
397
|
function transfer(address to, uint256 amount) public { }
|
|
398
398
|
// Selector: keccak256("transfer(address,uint256)")[:4]
|
|
399
399
|
|
|
400
|
-
//
|
|
400
|
+
// OP_NET: ALSO automatic via @method decorator
|
|
401
401
|
// @method({ name: 'to', type: ABIDataTypes.ADDRESS }, ...)
|
|
402
402
|
// public transfer(calldata: Calldata): BytesWriter { }
|
|
403
403
|
```
|
|
404
404
|
|
|
405
|
-
**Note:** Both Solidity and
|
|
405
|
+
**Note:** Both Solidity and OP_NET handle selector generation automatically. In OP_NET, use `@method` decorators and the runtime handles routing.
|
|
406
406
|
|
|
407
407
|
### Built-in Methods
|
|
408
408
|
|
|
@@ -444,7 +444,7 @@ contract MyContract is Ownable {
|
|
|
444
444
|
}
|
|
445
445
|
}
|
|
446
446
|
|
|
447
|
-
//
|
|
447
|
+
// OP_NET: Built-in onlyDeployer check
|
|
448
448
|
// this.onlyDeployer(Blockchain.tx.sender);
|
|
449
449
|
```
|
|
450
450
|
|
|
@@ -483,7 +483,7 @@ function setParameter(uint256 value) public onlyAdmin {
|
|
|
483
483
|
// ...
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
-
//
|
|
486
|
+
// OP_NET: Similar pattern but with explicit method call
|
|
487
487
|
// this.onlyAdmin(); at start of method
|
|
488
488
|
```
|
|
489
489
|
|
|
@@ -509,7 +509,7 @@ event Transfer(address indexed from, address indexed to, uint256 value);
|
|
|
509
509
|
|
|
510
510
|
emit Transfer(from, to, amount);
|
|
511
511
|
|
|
512
|
-
//
|
|
512
|
+
// OP_NET: emitEvent method
|
|
513
513
|
this.emitEvent(new TransferEvent(from, to, amount));
|
|
514
514
|
```
|
|
515
515
|
|
|
@@ -570,7 +570,7 @@ contract MyContract {
|
|
|
570
570
|
bytes private data; // slot 2
|
|
571
571
|
}
|
|
572
572
|
|
|
573
|
-
//
|
|
573
|
+
// OP_NET: Explicit pointer allocation
|
|
574
574
|
// private counterPointer: u16 = Blockchain.nextPointer;
|
|
575
575
|
// private counter: StoredU256 = new StoredU256(this.counterPointer, EMPTY_POINTER);
|
|
576
576
|
```
|
|
@@ -596,7 +596,7 @@ export class MyContract extends OP_NET {
|
|
|
596
596
|
// Solidity: mapping declaration
|
|
597
597
|
mapping(address => uint256) private balances;
|
|
598
598
|
|
|
599
|
-
//
|
|
599
|
+
// OP_NET: AddressMemoryMap with pointer
|
|
600
600
|
// private balancesPointer: u16 = Blockchain.nextPointer;
|
|
601
601
|
// this.balances = new AddressMemoryMap(this.balancesPointer);
|
|
602
602
|
```
|
|
@@ -750,7 +750,7 @@ contract MyToken is ERC20, Pausable {
|
|
|
750
750
|
}
|
|
751
751
|
}
|
|
752
752
|
|
|
753
|
-
//
|
|
753
|
+
// OP_NET: Custom Pausable base class
|
|
754
754
|
// export abstract class Pausable extends OP_NET { ... }
|
|
755
755
|
// this.whenNotPaused(); at start of method
|
|
756
756
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OP20 Token Standard
|
|
2
2
|
|
|
3
|
-
OP20 is
|
|
3
|
+
OP20 is OP_NET's fungible token standard, equivalent to Ethereum's ERC20. It provides a complete implementation for creating tokens with transfer, approval, and balance tracking capabilities.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -38,7 +38,7 @@ export class MyToken extends OP20 {
|
|
|
38
38
|
|
|
39
39
|
## ERC20 vs OP20 Comparison
|
|
40
40
|
|
|
41
|
-
| Feature | ERC20 (Solidity) | OP20 (
|
|
41
|
+
| Feature | ERC20 (Solidity) | OP20 (OP_NET) |
|
|
42
42
|
|---------|------------------|--------------|
|
|
43
43
|
| Language | Solidity | AssemblyScript |
|
|
44
44
|
| Runtime | EVM | WASM |
|
|
@@ -286,7 +286,7 @@ flowchart LR
|
|
|
286
286
|
<table>
|
|
287
287
|
<tr>
|
|
288
288
|
<th>ERC20 (Solidity)</th>
|
|
289
|
-
<th>OP20 (
|
|
289
|
+
<th>OP20 (OP_NET)</th>
|
|
290
290
|
</tr>
|
|
291
291
|
<tr>
|
|
292
292
|
<td>
|
|
@@ -39,7 +39,7 @@ export class MyToken extends OP20S {
|
|
|
39
39
|
|
|
40
40
|
## ERC20Permit vs OP20S Comparison
|
|
41
41
|
|
|
42
|
-
| Feature | ERC20Permit (EIP-2612) | OP20S (
|
|
42
|
+
| Feature | ERC20Permit (EIP-2612) | OP20S (OP_NET) |
|
|
43
43
|
|---------|------------------------|---------------|
|
|
44
44
|
| Language | Solidity | AssemblyScript |
|
|
45
45
|
| Signature Type | ECDSA (v, r, s) | Schnorr, ECDSA (deprecated), or ML-DSA |
|
|
@@ -327,7 +327,7 @@ contract.permit(..., nonce=0, signature0); // FAILS - nonce mismatch
|
|
|
327
327
|
<table>
|
|
328
328
|
<tr>
|
|
329
329
|
<th>ERC20Permit (Solidity)</th>
|
|
330
|
-
<th>OP20S (
|
|
330
|
+
<th>OP20S (OP_NET)</th>
|
|
331
331
|
</tr>
|
|
332
332
|
<tr>
|
|
333
333
|
<td>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OP721 NFT Standard
|
|
2
2
|
|
|
3
|
-
OP721 is
|
|
3
|
+
OP721 is OP_NET's non-fungible token standard, equivalent to Ethereum's ERC721. It provides a complete implementation for creating NFTs with ownership tracking, transfers, approvals, and metadata management.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -33,7 +33,7 @@ export class MyNFT extends OP721 {
|
|
|
33
33
|
|
|
34
34
|
## ERC721 vs OP721 Comparison
|
|
35
35
|
|
|
36
|
-
| Feature | ERC721 (Solidity) | OP721 (
|
|
36
|
+
| Feature | ERC721 (Solidity) | OP721 (OP_NET) |
|
|
37
37
|
|---------|-------------------|---------------|
|
|
38
38
|
| Language | Solidity | AssemblyScript |
|
|
39
39
|
| Runtime | EVM | WASM |
|
|
@@ -340,7 +340,7 @@ stateDiagram-v2
|
|
|
340
340
|
<table>
|
|
341
341
|
<tr>
|
|
342
342
|
<th>ERC721 (Solidity)</th>
|
|
343
|
-
<th>OP721 (
|
|
343
|
+
<th>OP721 (OP_NET)</th>
|
|
344
344
|
</tr>
|
|
345
345
|
<tr>
|
|
346
346
|
<td>
|
|
@@ -31,9 +31,9 @@ export class MyContract extends ReentrancyGuard {
|
|
|
31
31
|
}
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
## OpenZeppelin vs
|
|
34
|
+
## OpenZeppelin vs OP_NET ReentrancyGuard
|
|
35
35
|
|
|
36
|
-
| Feature | OpenZeppelin (Solidity) |
|
|
36
|
+
| Feature | OpenZeppelin (Solidity) | OP_NET ReentrancyGuard |
|
|
37
37
|
|---------|-------------------------|----------------------|
|
|
38
38
|
| Protection Scope | Per-function (`nonReentrant` modifier) | All methods by default |
|
|
39
39
|
| Opt-in/Opt-out | Opt-in per function | Opt-out via `isSelectorExcluded` |
|
|
@@ -529,8 +529,6 @@ The base `ReentrancyGuard` automatically excludes standard token receiver callba
|
|
|
529
529
|
// Built-in exclusions in ReentrancyGuard base class:
|
|
530
530
|
// - ON_OP20_RECEIVED_SELECTOR
|
|
531
531
|
// - ON_OP721_RECEIVED_SELECTOR
|
|
532
|
-
// - ON_OP1155_RECEIVED_MAGIC
|
|
533
|
-
// - ON_OP1155_BATCH_RECEIVED_MAGIC
|
|
534
532
|
```
|
|
535
533
|
|
|
536
534
|
You can override `isSelectorExcluded` to add custom exclusions:
|
|
@@ -564,7 +562,7 @@ export class MyContract extends ReentrancyGuard {
|
|
|
564
562
|
<table>
|
|
565
563
|
<tr>
|
|
566
564
|
<th>OpenZeppelin ReentrancyGuard</th>
|
|
567
|
-
<th>
|
|
565
|
+
<th>OP_NET ReentrancyGuard</th>
|
|
568
566
|
</tr>
|
|
569
567
|
<tr>
|
|
570
568
|
<td>
|
|
@@ -615,7 +613,7 @@ export class MyContract extends ReentrancyGuard {
|
|
|
615
613
|
|
|
616
614
|
Key differences:
|
|
617
615
|
- Solidity: Explicit `nonReentrant` modifier per function
|
|
618
|
-
-
|
|
616
|
+
- OP_NET: All methods protected by default (opt-out via `isSelectorExcluded`)
|
|
619
617
|
|
|
620
618
|
## Best Practices
|
|
621
619
|
|
|
@@ -731,7 +729,7 @@ public getValue(): u256 {
|
|
|
731
729
|
return value;
|
|
732
730
|
}
|
|
733
731
|
|
|
734
|
-
// In
|
|
732
|
+
// In OP_NET, the guard is checked at method entry,
|
|
735
733
|
// so this isn't an issue - just be aware of it
|
|
736
734
|
```
|
|
737
735
|
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Updatable
|
|
2
|
+
|
|
3
|
+
The `Updatable` base class provides a secure update mechanism with configurable timelock protection. Contracts extending `Updatable` 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 [`UpdatablePlugin`](../advanced/updatable#using-the-updatableplugin) instead. Just call `this.registerPlugin(new UpdatablePlugin(144))` in your constructor - no other code changes needed!
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import {
|
|
11
|
+
Updatable,
|
|
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 Updatable {
|
|
21
|
+
// Set delay: 144 blocks = ~24 hours
|
|
22
|
+
protected readonly updateDelay: u64 = 144;
|
|
23
|
+
|
|
24
|
+
public override execute(method: Selector, calldata: Calldata): BytesWriter {
|
|
25
|
+
switch (method) {
|
|
26
|
+
case encodeSelector('submitUpdate'):
|
|
27
|
+
return this.submitUpdate(calldata.readAddress());
|
|
28
|
+
case encodeSelector('applyUpdate'): {
|
|
29
|
+
const sourceAddress = calldata.readAddress();
|
|
30
|
+
const updateCalldata = calldata.readBytesWithLength();
|
|
31
|
+
return this.applyUpdate(sourceAddress, updateCalldata);
|
|
32
|
+
}
|
|
33
|
+
case encodeSelector('cancelUpdate'):
|
|
34
|
+
return this.cancelUpdate();
|
|
35
|
+
default:
|
|
36
|
+
return super.execute(method, calldata);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Class Reference
|
|
43
|
+
|
|
44
|
+
### Properties
|
|
45
|
+
|
|
46
|
+
| Property | Type | Description |
|
|
47
|
+
|----------|------|-------------|
|
|
48
|
+
| `updateDelay` | `u64` | Blocks to wait before update can be applied (default: 144 = ~24h) |
|
|
49
|
+
| `pendingUpdateAddress` | `Address` | Source address of pending update (zero if none) |
|
|
50
|
+
| `pendingUpdateBlock` | `u64` | Block when update was submitted (0 if none) |
|
|
51
|
+
| `updateEffectiveBlock` | `u64` | Block when update can be applied (0 if none) |
|
|
52
|
+
| `hasPendingUpdate` | `bool` | Whether an update is pending |
|
|
53
|
+
| `canApplyUpdate` | `bool` | Whether the delay has elapsed |
|
|
54
|
+
|
|
55
|
+
### Methods
|
|
56
|
+
|
|
57
|
+
#### submitUpdate
|
|
58
|
+
|
|
59
|
+
Submits an update for timelock. Only callable by deployer.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
protected submitUpdate(sourceAddress: Address): BytesWriter
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Parameters:**
|
|
66
|
+
- `sourceAddress`: The address of the contract containing the new bytecode
|
|
67
|
+
|
|
68
|
+
**Reverts if:**
|
|
69
|
+
- Caller is not the deployer
|
|
70
|
+
- Source is not a deployed contract
|
|
71
|
+
- An update is already pending
|
|
72
|
+
|
|
73
|
+
**Emits:** `UpdateSubmitted(sourceAddress, submitBlock, effectiveBlock)`
|
|
74
|
+
|
|
75
|
+
#### applyUpdate
|
|
76
|
+
|
|
77
|
+
Applies a pending update after the delay has passed. Only callable by deployer.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
protected applyUpdate(sourceAddress: Address, calldata: BytesWriter): BytesWriter
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Parameters:**
|
|
84
|
+
- `sourceAddress`: The source contract address (must match pending)
|
|
85
|
+
- `calldata`: Data passed to the `onUpdate` hook of the new contract
|
|
86
|
+
|
|
87
|
+
**Reverts if:**
|
|
88
|
+
- Caller is not the deployer
|
|
89
|
+
- No update is pending
|
|
90
|
+
- Delay has not elapsed
|
|
91
|
+
- Address does not match pending update
|
|
92
|
+
|
|
93
|
+
**Emits:** `UpdateApplied(sourceAddress, appliedAtBlock)`
|
|
94
|
+
|
|
95
|
+
#### cancelUpdate
|
|
96
|
+
|
|
97
|
+
Cancels a pending update. Only callable by deployer.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
protected cancelUpdate(): BytesWriter
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Reverts if:**
|
|
104
|
+
- Caller is not the deployer
|
|
105
|
+
- No update is pending
|
|
106
|
+
|
|
107
|
+
**Emits:** `UpdateCancelled(sourceAddress, cancelledAtBlock)`
|
|
108
|
+
|
|
109
|
+
## Events
|
|
110
|
+
|
|
111
|
+
### UpdateSubmittedEvent
|
|
112
|
+
|
|
113
|
+
Emitted when an update is submitted.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
class UpdateSubmittedEvent extends NetEvent {
|
|
117
|
+
constructor(
|
|
118
|
+
sourceAddress: Address, // Contract with new bytecode
|
|
119
|
+
submitBlock: u64, // Block when submitted
|
|
120
|
+
effectiveBlock: u64 // Block when can be applied
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### UpdateAppliedEvent
|
|
126
|
+
|
|
127
|
+
Emitted when an update is applied.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
class UpdateAppliedEvent extends NetEvent {
|
|
131
|
+
constructor(
|
|
132
|
+
sourceAddress: Address, // Contract with new bytecode
|
|
133
|
+
appliedAtBlock: u64 // Block when applied
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### UpdateCancelledEvent
|
|
139
|
+
|
|
140
|
+
Emitted when a pending update is cancelled.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
class UpdateCancelledEvent extends NetEvent {
|
|
144
|
+
constructor(
|
|
145
|
+
sourceAddress: Address, // Cancelled source contract
|
|
146
|
+
cancelledAtBlock: u64 // Block when cancelled
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Usage Patterns
|
|
152
|
+
|
|
153
|
+
### Basic Updatable Contract
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
@final
|
|
157
|
+
export class SimpleUpdatable extends Updatable {
|
|
158
|
+
protected readonly updateDelay: u64 = 144; // ~1 day
|
|
159
|
+
|
|
160
|
+
public override execute(method: Selector, calldata: Calldata): BytesWriter {
|
|
161
|
+
switch (method) {
|
|
162
|
+
case encodeSelector('submitUpdate'):
|
|
163
|
+
return this.submitUpdate(calldata.readAddress());
|
|
164
|
+
case encodeSelector('applyUpdate'): {
|
|
165
|
+
const sourceAddress = calldata.readAddress();
|
|
166
|
+
const updateCalldata = calldata.readBytesWithLength();
|
|
167
|
+
return this.applyUpdate(sourceAddress, updateCalldata);
|
|
168
|
+
}
|
|
169
|
+
case encodeSelector('cancelUpdate'):
|
|
170
|
+
return this.cancelUpdate();
|
|
171
|
+
default:
|
|
172
|
+
return super.execute(method, calldata);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### With Update Status Views
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
@final
|
|
182
|
+
export class UpdatableWithViews extends Updatable {
|
|
183
|
+
protected readonly updateDelay: u64 = 1008; // ~1 week
|
|
184
|
+
|
|
185
|
+
public override execute(method: Selector, calldata: Calldata): BytesWriter {
|
|
186
|
+
switch (method) {
|
|
187
|
+
// Update actions
|
|
188
|
+
case encodeSelector('submitUpdate'):
|
|
189
|
+
return this.submitUpdate(calldata.readAddress());
|
|
190
|
+
case encodeSelector('applyUpdate'): {
|
|
191
|
+
const sourceAddress = calldata.readAddress();
|
|
192
|
+
const updateCalldata = calldata.readBytesWithLength();
|
|
193
|
+
return this.applyUpdate(sourceAddress, updateCalldata);
|
|
194
|
+
}
|
|
195
|
+
case encodeSelector('cancelUpdate'):
|
|
196
|
+
return this.cancelUpdate();
|
|
197
|
+
|
|
198
|
+
// View methods
|
|
199
|
+
case encodeSelector('getPendingUpdate'):
|
|
200
|
+
return this.getPendingUpdate();
|
|
201
|
+
case encodeSelector('getUpdateStatus'):
|
|
202
|
+
return this.getUpdateStatus();
|
|
203
|
+
|
|
204
|
+
default:
|
|
205
|
+
return super.execute(method, calldata);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private getPendingUpdate(): BytesWriter {
|
|
210
|
+
const response = new BytesWriter(32);
|
|
211
|
+
response.writeAddress(this.pendingUpdateAddress);
|
|
212
|
+
return response;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private getUpdateStatus(): BytesWriter {
|
|
216
|
+
const response = new BytesWriter(17);
|
|
217
|
+
response.writeBoolean(this.hasPendingUpdate);
|
|
218
|
+
response.writeU64(this.pendingUpdateBlock);
|
|
219
|
+
response.writeU64(this.updateEffectiveBlock);
|
|
220
|
+
return response;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Emergency Updates (Not Recommended)
|
|
226
|
+
|
|
227
|
+
For contracts that need faster updates (use with caution):
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
@final
|
|
231
|
+
export class QuickUpdatable extends Updatable {
|
|
232
|
+
// Only 6 blocks (~1 hour) - use only for emergencies
|
|
233
|
+
protected readonly updateDelay: u64 = 6;
|
|
234
|
+
|
|
235
|
+
// ... rest of implementation
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Security Considerations
|
|
240
|
+
|
|
241
|
+
### 1. Delay Selection
|
|
242
|
+
|
|
243
|
+
Choose an appropriate delay based on your contract's risk profile:
|
|
244
|
+
|
|
245
|
+
| Contract Type | Recommended Delay |
|
|
246
|
+
|---------------|-------------------|
|
|
247
|
+
| Test/Development | 1-6 blocks |
|
|
248
|
+
| Standard DeFi | 144 blocks (~24 hours) |
|
|
249
|
+
| High-value vaults | 1008 blocks (~1 week) |
|
|
250
|
+
| Governance contracts | 4320 blocks (~1 month) |
|
|
251
|
+
|
|
252
|
+
### 2. Source Validation
|
|
253
|
+
|
|
254
|
+
The `submitUpdate` function validates that the source is a deployed contract. This prevents:
|
|
255
|
+
- Submitting non-existent addresses
|
|
256
|
+
- Last-minute malicious deployments
|
|
257
|
+
|
|
258
|
+
### 3. Address Match Verification
|
|
259
|
+
|
|
260
|
+
The `applyUpdate` function requires the address to match the pending update. This prevents:
|
|
261
|
+
- Front-running attacks
|
|
262
|
+
- Address substitution attacks
|
|
263
|
+
|
|
264
|
+
### 4. Storage Compatibility
|
|
265
|
+
|
|
266
|
+
When upgrading, ensure storage layout compatibility:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
// V1 - Original
|
|
270
|
+
class ContractV1 extends Updatable {
|
|
271
|
+
private ptr1: u16 = Blockchain.nextPointer; // Pointer 1
|
|
272
|
+
private ptr2: u16 = Blockchain.nextPointer; // Pointer 2
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// V2 - Add new pointers at the END
|
|
276
|
+
class ContractV2 extends Updatable {
|
|
277
|
+
private ptr1: u16 = Blockchain.nextPointer; // Pointer 1 (same)
|
|
278
|
+
private ptr2: u16 = Blockchain.nextPointer; // Pointer 2 (same)
|
|
279
|
+
private ptr3: u16 = Blockchain.nextPointer; // Pointer 3 (NEW)
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Update Workflow
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
1. Deploy new bytecode contract
|
|
287
|
+
└─> Returns: newContractAddress
|
|
288
|
+
|
|
289
|
+
2. Submit update
|
|
290
|
+
└─> submitUpdate(newContractAddress)
|
|
291
|
+
└─> Emits: UpdateSubmitted
|
|
292
|
+
|
|
293
|
+
3. Wait for delay
|
|
294
|
+
└─> Users can monitor and exit
|
|
295
|
+
|
|
296
|
+
4. Apply update
|
|
297
|
+
└─> applyUpdate(newContractAddress)
|
|
298
|
+
└─> Emits: UpdateApplied
|
|
299
|
+
└─> VM calls onUpdate() on new bytecode
|
|
300
|
+
└─> New bytecode active next block
|
|
301
|
+
|
|
302
|
+
5. (Optional) Discard source contract
|
|
303
|
+
└─> Source contract can be abandoned
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### The onUpdate Hook
|
|
307
|
+
|
|
308
|
+
Override `onUpdate` in your new contract version to perform migrations:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
@final
|
|
312
|
+
export class MyContractV2 extends Updatable {
|
|
313
|
+
protected readonly updateDelay: u64 = 144;
|
|
314
|
+
|
|
315
|
+
// New storage added in V2
|
|
316
|
+
private newFeaturePointer: u16 = Blockchain.nextPointer;
|
|
317
|
+
private _newFeature: StoredU256;
|
|
318
|
+
|
|
319
|
+
public constructor() {
|
|
320
|
+
super();
|
|
321
|
+
this._newFeature = new StoredU256(this.newFeaturePointer, EMPTY_POINTER);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
public override onUpdate(calldata: Calldata): void {
|
|
325
|
+
super.onUpdate(calldata);
|
|
326
|
+
// Initialize new storage
|
|
327
|
+
this._newFeature.value = u256.fromU64(100);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ... execute() and other methods
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
See [The onUpdate Lifecycle Hook](../advanced/updatable#the-onupdate-lifecycle-hook) for more details.
|
|
335
|
+
|
|
336
|
+
## Combining with Other Base Classes
|
|
337
|
+
|
|
338
|
+
### Updatable + ReentrancyGuard
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// Create a combined base class
|
|
342
|
+
class UpdatableWithReentrancy extends Updatable {
|
|
343
|
+
protected readonly reentrancyLevel: ReentrancyLevel = ReentrancyLevel.STANDARD;
|
|
344
|
+
private _locked: StoredBoolean;
|
|
345
|
+
|
|
346
|
+
protected constructor() {
|
|
347
|
+
super();
|
|
348
|
+
this._locked = new StoredBoolean(Blockchain.nextPointer, false);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
protected nonReentrant(): void {
|
|
352
|
+
if (this._locked.value) {
|
|
353
|
+
throw new Revert('ReentrancyGuard: LOCKED');
|
|
354
|
+
}
|
|
355
|
+
this._locked.value = true;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
protected releaseGuard(): void {
|
|
359
|
+
this._locked.value = false;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
@final
|
|
364
|
+
export class SecureUpdatableVault extends UpdatableWithReentrancy {
|
|
365
|
+
// Implementation with both update and reentrancy protection
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Solidity Comparison
|
|
370
|
+
|
|
371
|
+
| Feature | OpenZeppelin UUPS | OP_NET Updatable |
|
|
372
|
+
|---------|-------------------|-------------------|
|
|
373
|
+
| Update mechanism | delegatecall | Native bytecode replacement |
|
|
374
|
+
| Storage location | Implementation contract | Same contract |
|
|
375
|
+
| Proxy overhead | Yes | No |
|
|
376
|
+
| Timelock | Separate contract (optional) | Built-in |
|
|
377
|
+
| Events | Custom | Built-in |
|
|
378
|
+
| Cancel update | Custom implementation | Built-in |
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
**Navigation:**
|
|
383
|
+
- Previous: [ReentrancyGuard](./reentrancy-guard.md)
|
|
384
|
+
- Next: [Address Type](../types/address.md)
|