@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,396 +0,0 @@
|
|
|
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)
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
2
|
-
import { Blockchain } from '../env';
|
|
3
|
-
import { OP_NET } from './OP_NET';
|
|
4
|
-
import { StoredAddress } from '../storage/StoredAddress';
|
|
5
|
-
import { StoredU256 } from '../storage/StoredU256';
|
|
6
|
-
import { Address } from '../types/Address';
|
|
7
|
-
import { Revert } from '../types/Revert';
|
|
8
|
-
import { BytesWriter } from '../buffer/BytesWriter';
|
|
9
|
-
import { EMPTY_POINTER } from '../math/bytes';
|
|
10
|
-
import {
|
|
11
|
-
UpgradeAppliedEvent,
|
|
12
|
-
UpgradeCancelledEvent,
|
|
13
|
-
UpgradeSubmittedEvent,
|
|
14
|
-
} from '../events/upgradeable/UpgradeableEvents';
|
|
15
|
-
|
|
16
|
-
const pendingUpgradeAddressPointer: u16 = Blockchain.nextPointer;
|
|
17
|
-
const pendingUpgradeBlockPointer: u16 = Blockchain.nextPointer;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Upgradeable - Base contract for upgradeable contracts with timelock protection.
|
|
21
|
-
*
|
|
22
|
-
* This contract provides a secure upgrade mechanism with a configurable delay period.
|
|
23
|
-
* The pattern prevents instant malicious upgrades by requiring:
|
|
24
|
-
* 1. submitUpgrade() - Submit the source contract address, starts the timelock
|
|
25
|
-
* 2. Wait for the delay period to pass
|
|
26
|
-
* 3. applyUpgrade() - Apply the upgrade after the delay
|
|
27
|
-
*
|
|
28
|
-
* Users can monitor for UpgradeSubmitted events and exit if they distrust pending changes.
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```typescript
|
|
32
|
-
* @final
|
|
33
|
-
* export class MyUpgradeableContract extends Upgradeable {
|
|
34
|
-
* // Set a 24-hour delay (144 blocks at 10 min/block)
|
|
35
|
-
* protected readonly upgradeDelay: u64 = 144;
|
|
36
|
-
*
|
|
37
|
-
* public override execute(method: Selector, calldata: Calldata): BytesWriter {
|
|
38
|
-
* switch (method) {
|
|
39
|
-
* case encodeSelector('submitUpgrade'):
|
|
40
|
-
* return this.submitUpgrade(calldata.readAddress());
|
|
41
|
-
* case encodeSelector('applyUpgrade'):
|
|
42
|
-
* const sourceAddress = calldata.readAddress();
|
|
43
|
-
* const updateCalldata = new BytesWriter(calldata.byteLength - ADDRESS_BYTE_LENGTH);
|
|
44
|
-
* // Copy remaining calldata for onUpdate
|
|
45
|
-
* return this.applyUpgrade(sourceAddress, updateCalldata);
|
|
46
|
-
* case encodeSelector('cancelUpgrade'):
|
|
47
|
-
* return this.cancelUpgrade();
|
|
48
|
-
* default:
|
|
49
|
-
* return super.execute(method, calldata);
|
|
50
|
-
* }
|
|
51
|
-
* }
|
|
52
|
-
* }
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
55
|
-
export class Upgradeable extends OP_NET {
|
|
56
|
-
/**
|
|
57
|
-
* The pending upgrade source address.
|
|
58
|
-
* Zero address means no pending upgrade.
|
|
59
|
-
*/
|
|
60
|
-
protected readonly _pendingUpgradeAddress: StoredAddress;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* The block number when the upgrade was submitted.
|
|
64
|
-
* Stored as u256, used as u64.
|
|
65
|
-
*/
|
|
66
|
-
protected readonly _pendingUpgradeBlock: StoredU256;
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* The number of blocks to wait before an upgrade can be applied.
|
|
70
|
-
* Override this in derived contracts to set the delay.
|
|
71
|
-
*
|
|
72
|
-
* Common values:
|
|
73
|
-
* - 6 blocks = ~1 hour
|
|
74
|
-
* - 144 blocks = ~24 hours
|
|
75
|
-
* - 1008 blocks = ~1 week
|
|
76
|
-
*/
|
|
77
|
-
protected readonly upgradeDelay: u64 = 144; // ~24 hours default
|
|
78
|
-
|
|
79
|
-
protected constructor() {
|
|
80
|
-
super();
|
|
81
|
-
this._pendingUpgradeAddress = new StoredAddress(pendingUpgradeAddressPointer);
|
|
82
|
-
this._pendingUpgradeBlock = new StoredU256(pendingUpgradeBlockPointer, EMPTY_POINTER);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Returns the pending upgrade source address.
|
|
87
|
-
* Returns zero address if no upgrade is pending.
|
|
88
|
-
*/
|
|
89
|
-
public get pendingUpgradeAddress(): Address {
|
|
90
|
-
return this._pendingUpgradeAddress.value;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Returns the block number when the pending upgrade was submitted.
|
|
95
|
-
* Returns 0 if no upgrade is pending.
|
|
96
|
-
*/
|
|
97
|
-
public get pendingUpgradeBlock(): u64 {
|
|
98
|
-
return this._pendingUpgradeBlock.value.lo1;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Returns the block number when the pending upgrade can be applied.
|
|
103
|
-
* Returns 0 if no upgrade is pending.
|
|
104
|
-
*/
|
|
105
|
-
public get upgradeEffectiveBlock(): u64 {
|
|
106
|
-
const submitBlock = this.pendingUpgradeBlock;
|
|
107
|
-
if (submitBlock === 0) return 0;
|
|
108
|
-
return submitBlock + this.upgradeDelay;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Returns true if there is a pending upgrade.
|
|
113
|
-
*/
|
|
114
|
-
public get hasPendingUpgrade(): bool {
|
|
115
|
-
return this.pendingUpgradeBlock !== 0;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Returns true if the pending upgrade can be applied (delay has passed).
|
|
120
|
-
*/
|
|
121
|
-
public get canApplyUpgrade(): bool {
|
|
122
|
-
if (!this.hasPendingUpgrade) return false;
|
|
123
|
-
return Blockchain.block.number >= this.upgradeEffectiveBlock;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Submits an upgrade for timelock.
|
|
128
|
-
*
|
|
129
|
-
* The source address must be a deployed contract containing the new bytecode.
|
|
130
|
-
* After submission, the upgrade can only be applied after upgradeDelay blocks.
|
|
131
|
-
*
|
|
132
|
-
* Emits UpgradeSubmitted event.
|
|
133
|
-
*
|
|
134
|
-
* @param sourceAddress - The source contract address containing new bytecode
|
|
135
|
-
* @returns Empty response
|
|
136
|
-
* @throws If caller is not deployer
|
|
137
|
-
* @throws If source is not a deployed contract
|
|
138
|
-
* @throws If an upgrade is already pending
|
|
139
|
-
*/
|
|
140
|
-
protected submitUpgrade(sourceAddress: Address): BytesWriter {
|
|
141
|
-
this.onlyDeployer(Blockchain.tx.sender);
|
|
142
|
-
|
|
143
|
-
// Check no pending upgrade
|
|
144
|
-
if (this.hasPendingUpgrade) {
|
|
145
|
-
throw new Revert('Upgrade already pending. Cancel first.');
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Validate source is a deployed contract
|
|
149
|
-
if (!Blockchain.isContract(sourceAddress)) {
|
|
150
|
-
throw new Revert('Source must be a deployed contract');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Store pending upgrade
|
|
154
|
-
const currentBlock = Blockchain.block.number;
|
|
155
|
-
this._pendingUpgradeAddress.value = sourceAddress;
|
|
156
|
-
this._pendingUpgradeBlock.value = u256.fromU64(currentBlock);
|
|
157
|
-
|
|
158
|
-
// Emit event
|
|
159
|
-
const effectiveBlock = currentBlock + this.upgradeDelay;
|
|
160
|
-
Blockchain.emit(new UpgradeSubmittedEvent(sourceAddress, currentBlock, effectiveBlock));
|
|
161
|
-
|
|
162
|
-
return new BytesWriter(0);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Applies a pending upgrade after the timelock period has passed.
|
|
167
|
-
*
|
|
168
|
-
* The provided address must match the pending upgrade address as an
|
|
169
|
-
* additional security measure against front-running attacks.
|
|
170
|
-
*
|
|
171
|
-
* Emits UpgradeApplied event before the upgrade (new bytecode takes effect next block).
|
|
172
|
-
*
|
|
173
|
-
* @param sourceAddress - The source contract address (must match pending)
|
|
174
|
-
* @param calldata - The calldata to pass to onUpdate method of the new contract
|
|
175
|
-
* @returns Empty response
|
|
176
|
-
* @throws If caller is not deployer
|
|
177
|
-
* @throws If no upgrade is pending
|
|
178
|
-
* @throws If delay has not passed
|
|
179
|
-
* @throws If provided address does not match pending
|
|
180
|
-
*/
|
|
181
|
-
protected applyUpgrade(sourceAddress: Address, calldata: BytesWriter): BytesWriter {
|
|
182
|
-
this.onlyDeployer(Blockchain.tx.sender);
|
|
183
|
-
|
|
184
|
-
// Check pending upgrade exists
|
|
185
|
-
if (!this.hasPendingUpgrade) {
|
|
186
|
-
throw new Revert('No pending upgrade');
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Check delay has passed
|
|
190
|
-
if (!this.canApplyUpgrade) {
|
|
191
|
-
throw new Revert('Upgrade delay not elapsed');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Verify address matches pending
|
|
195
|
-
if (!sourceAddress.equals(this._pendingUpgradeAddress.value)) {
|
|
196
|
-
throw new Revert('Address does not match pending upgrade');
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Clear pending state before upgrade
|
|
200
|
-
this._pendingUpgradeAddress.value = Address.zero();
|
|
201
|
-
this._pendingUpgradeBlock.value = u256.Zero;
|
|
202
|
-
|
|
203
|
-
// Emit event
|
|
204
|
-
Blockchain.emit(new UpgradeAppliedEvent(sourceAddress, Blockchain.block.number));
|
|
205
|
-
|
|
206
|
-
// Perform upgrade - new bytecode takes effect next block
|
|
207
|
-
Blockchain.updateContractFromExisting(sourceAddress, calldata);
|
|
208
|
-
|
|
209
|
-
return new BytesWriter(0);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Cancels a pending upgrade.
|
|
214
|
-
*
|
|
215
|
-
* Can only be called by the deployer. Clears the pending upgrade state.
|
|
216
|
-
*
|
|
217
|
-
* Emits UpgradeCancelled event.
|
|
218
|
-
*
|
|
219
|
-
* @returns Empty response
|
|
220
|
-
* @throws If caller is not deployer
|
|
221
|
-
* @throws If no upgrade is pending
|
|
222
|
-
*/
|
|
223
|
-
protected cancelUpgrade(): BytesWriter {
|
|
224
|
-
this.onlyDeployer(Blockchain.tx.sender);
|
|
225
|
-
|
|
226
|
-
// Check pending upgrade exists
|
|
227
|
-
if (!this.hasPendingUpgrade) {
|
|
228
|
-
throw new Revert('No pending upgrade');
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const pendingAddress = this._pendingUpgradeAddress.value;
|
|
232
|
-
|
|
233
|
-
// Clear pending state
|
|
234
|
-
this._pendingUpgradeAddress.value = Address.zero();
|
|
235
|
-
this._pendingUpgradeBlock.value = u256.Zero;
|
|
236
|
-
|
|
237
|
-
// Emit event
|
|
238
|
-
Blockchain.emit(new UpgradeCancelledEvent(pendingAddress, Blockchain.block.number));
|
|
239
|
-
|
|
240
|
-
return new BytesWriter(0);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { BytesWriter } from '../../buffer/BytesWriter';
|
|
2
|
-
import { Calldata } from '../../types';
|
|
3
|
-
|
|
4
|
-
export interface IOP1155 {
|
|
5
|
-
// Core properties
|
|
6
|
-
fn_name(calldata: Calldata): BytesWriter;
|
|
7
|
-
fn_symbol(calldata: Calldata): BytesWriter;
|
|
8
|
-
uri(calldata: Calldata): BytesWriter;
|
|
9
|
-
|
|
10
|
-
// Balance and supply
|
|
11
|
-
balanceOf(calldata: Calldata): BytesWriter;
|
|
12
|
-
balanceOfBatch(calldata: Calldata): BytesWriter;
|
|
13
|
-
totalSupply(calldata: Calldata): BytesWriter;
|
|
14
|
-
exists(calldata: Calldata): BytesWriter;
|
|
15
|
-
|
|
16
|
-
// Transfer functions
|
|
17
|
-
safeTransferFrom(calldata: Calldata): BytesWriter;
|
|
18
|
-
safeBatchTransferFrom(calldata: Calldata): BytesWriter;
|
|
19
|
-
|
|
20
|
-
// Approval functions
|
|
21
|
-
setApprovalForAll(calldata: Calldata): BytesWriter;
|
|
22
|
-
isApprovedForAll(calldata: Calldata): BytesWriter;
|
|
23
|
-
|
|
24
|
-
// Advanced functions
|
|
25
|
-
burn(calldata: Calldata): BytesWriter;
|
|
26
|
-
burnBatch(calldata: Calldata): BytesWriter;
|
|
27
|
-
transferBySignature(calldata: Calldata): BytesWriter;
|
|
28
|
-
batchTransferBySignature(calldata: Calldata): BytesWriter;
|
|
29
|
-
domainSeparator(calldata: Calldata): BytesWriter;
|
|
30
|
-
|
|
31
|
-
// Interface support
|
|
32
|
-
supportsInterface(calldata: Calldata): BytesWriter;
|
|
33
|
-
}
|