@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.
Files changed (72) hide show
  1. package/README.md +7 -7
  2. package/docs/README.md +39 -39
  3. package/docs/advanced/bitcoin-scripts.md +17 -17
  4. package/docs/advanced/{contract-upgrades.md → contract-updates.md} +90 -98
  5. package/docs/advanced/cross-contract-calls.md +4 -4
  6. package/docs/advanced/plugins.md +21 -21
  7. package/docs/advanced/quantum-resistance.md +32 -32
  8. package/docs/advanced/signature-verification.md +22 -22
  9. package/docs/api-reference/blockchain.md +14 -14
  10. package/docs/api-reference/events.md +2 -2
  11. package/docs/api-reference/op20.md +7 -7
  12. package/docs/api-reference/op721.md +7 -7
  13. package/docs/api-reference/storage.md +2 -2
  14. package/docs/contracts/op-net-base.md +15 -15
  15. package/docs/contracts/op20-token.md +3 -3
  16. package/docs/contracts/op20s-signatures.md +2 -2
  17. package/docs/contracts/op721-nft.md +3 -3
  18. package/docs/contracts/reentrancy-guard.md +5 -7
  19. package/docs/contracts/updatable.md +384 -0
  20. package/docs/core-concepts/blockchain-environment.md +10 -10
  21. package/docs/core-concepts/decorators.md +5 -5
  22. package/docs/core-concepts/events.md +6 -6
  23. package/docs/core-concepts/pointers.md +5 -5
  24. package/docs/core-concepts/security.md +5 -5
  25. package/docs/core-concepts/storage-system.md +24 -24
  26. package/docs/examples/basic-token.md +8 -8
  27. package/docs/examples/nft-with-reservations.md +9 -9
  28. package/docs/examples/oracle-integration.md +13 -13
  29. package/docs/examples/stablecoin.md +10 -10
  30. package/docs/getting-started/first-contract.md +8 -8
  31. package/docs/getting-started/installation.md +2 -2
  32. package/docs/getting-started/project-structure.md +6 -6
  33. package/docs/storage/memory-maps.md +8 -8
  34. package/docs/storage/stored-arrays.md +6 -6
  35. package/docs/storage/stored-maps.md +8 -8
  36. package/docs/storage/stored-primitives.md +6 -6
  37. package/docs/types/address.md +13 -13
  38. package/docs/types/bytes-writer-reader.md +18 -18
  39. package/docs/types/calldata.md +12 -12
  40. package/package.json +10 -10
  41. package/runtime/constants/Exports.ts +0 -30
  42. package/runtime/contracts/OP20.ts +7 -7
  43. package/runtime/contracts/OP721.ts +60 -74
  44. package/runtime/contracts/OP_NET.ts +2 -2
  45. package/runtime/contracts/ReentrancyGuard.ts +1 -5
  46. package/runtime/contracts/Updatable.ts +241 -0
  47. package/runtime/contracts/interfaces/OP721InitParameters.ts +8 -8
  48. package/runtime/env/BlockchainEnvironment.ts +5 -5
  49. package/runtime/env/global.ts +7 -6
  50. package/runtime/events/predefined/{ApprovedEvent.ts → OP20ApprovedEvent.ts} +1 -1
  51. package/runtime/events/predefined/{BurnedEvent.ts → OP20BurnedEvent.ts} +1 -1
  52. package/runtime/events/predefined/{MintedEvent.ts → OP20MintedEvent.ts} +1 -1
  53. package/runtime/events/predefined/{TransferredEvent.ts → OP20TransferredEvent.ts} +1 -1
  54. package/runtime/events/predefined/OP721ApprovedEvent.ts +17 -0
  55. package/runtime/events/predefined/{ApprovedForAll.ts → OP721ApprovedForAllEvent.ts} +1 -1
  56. package/runtime/events/predefined/OP721BurnedEvent.ts +16 -0
  57. package/runtime/events/predefined/OP721MintedEvent.ts +16 -0
  58. package/runtime/events/predefined/OP721TransferredEvent.ts +18 -0
  59. package/runtime/events/predefined/index.ts +5 -5
  60. package/runtime/events/{upgradeable/UpgradeableEvents.ts → updatable/UpdatableEvents.ts} +9 -9
  61. package/runtime/hashing/keccak256.ts +1 -1
  62. package/runtime/index.ts +3 -5
  63. package/runtime/plugins/UpdatablePlugin.ts +276 -0
  64. package/runtime/script/Networks.ts +1 -1
  65. package/runtime/storage/StoredBoolean.ts +23 -12
  66. package/runtime/types/Address.ts +1 -1
  67. package/runtime/types/ExtendedAddress.ts +1 -1
  68. package/docs/contracts/upgradeable.md +0 -396
  69. package/runtime/contracts/Upgradeable.ts +0 -242
  70. package/runtime/contracts/interfaces/IOP1155.ts +0 -33
  71. package/runtime/contracts/interfaces/OP1155InitParameters.ts +0 -11
  72. package/runtime/plugins/UpgradeablePlugin.ts +0 -279
@@ -0,0 +1,276 @@
1
+ import { u256 } from '@btc-vision/as-bignum/assembly';
2
+ import { Blockchain } from '../env';
3
+ import { Plugin } from './Plugin';
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 { encodeSelector, Selector } from '../math/abi';
10
+ import { ADDRESS_BYTE_LENGTH } from '../utils';
11
+ import { Calldata } from '../types';
12
+ import { EMPTY_POINTER } from '../math/bytes';
13
+ import {
14
+ UpdateAppliedEvent,
15
+ UpdateCancelledEvent,
16
+ UpdateSubmittedEvent,
17
+ } from '../events/updatable/UpdatableEvents';
18
+
19
+ /**
20
+ * UpdatablePlugin - Plugin for updatable contracts with timelock protection.
21
+ *
22
+ * This plugin provides a secure update mechanism with a configurable delay period.
23
+ * Unlike extending the Updatable base class, this plugin can be added to any contract.
24
+ *
25
+ * The pattern prevents instant malicious updates by requiring:
26
+ * 1. submitUpdate() - Submit the source contract address, starts the timelock
27
+ * 2. Wait for the delay period to pass
28
+ * 3. applyUpdate() - Apply the update after the delay
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * @final
33
+ * export class MyContract extends OP_NET {
34
+ * public constructor() {
35
+ * super();
36
+ * // 144 blocks = ~24 hours
37
+ * this.registerPlugin(new UpdatablePlugin(144));
38
+ * }
39
+ *
40
+ * // No need to modify execute() - the plugin handles update methods automatically!
41
+ * }
42
+ * ```
43
+ */
44
+ export class UpdatablePlugin extends Plugin {
45
+ private readonly _pendingUpdateAddress: StoredAddress;
46
+ private readonly _pendingUpdateBlock: StoredU256;
47
+ private readonly _updateDelay: u64;
48
+
49
+ /**
50
+ * Creates a new UpdatablePlugin.
51
+ *
52
+ * @param updateDelay - Number of blocks to wait before update can be applied.
53
+ * Default: 144 blocks (~24 hours)
54
+ * Common values:
55
+ * - 6 blocks = ~1 hour
56
+ * - 144 blocks = ~24 hours
57
+ * - 1008 blocks = ~1 week
58
+ * @param addressPointer - Storage pointer for pending update address
59
+ * @param blockPointer - Storage pointer for pending update block
60
+ */
61
+ public constructor(
62
+ updateDelay: u64 = 144,
63
+ addressPointer: u16 = Blockchain.nextPointer,
64
+ blockPointer: u16 = Blockchain.nextPointer,
65
+ ) {
66
+ super();
67
+ this._updateDelay = updateDelay;
68
+ this._pendingUpdateAddress = new StoredAddress(addressPointer);
69
+ this._pendingUpdateBlock = new StoredU256(blockPointer, EMPTY_POINTER);
70
+ }
71
+
72
+ // Method selectors
73
+ public static get SUBMIT_UPDATE_SELECTOR(): Selector {
74
+ return encodeSelector('submitUpdate(address)');
75
+ }
76
+
77
+ public static get APPLY_UPDATE_SELECTOR(): Selector {
78
+ return encodeSelector('applyUpdate(address,bytes)');
79
+ }
80
+
81
+ public static get CANCEL_UPDATE_SELECTOR(): Selector {
82
+ return encodeSelector('cancelUpdate()');
83
+ }
84
+
85
+ public static get PENDING_UPDATE_SELECTOR(): Selector {
86
+ return encodeSelector('pendingUpdate()');
87
+ }
88
+
89
+ public static get UPDATE_DELAY_SELECTOR(): Selector {
90
+ return encodeSelector('updateDelay()');
91
+ }
92
+
93
+ /**
94
+ * Returns the pending update source address.
95
+ */
96
+ public get pendingUpdateAddress(): Address {
97
+ return this._pendingUpdateAddress.value;
98
+ }
99
+
100
+ /**
101
+ * Returns the block number when the pending update was submitted.
102
+ */
103
+ public get pendingUpdateBlock(): u64 {
104
+ return this._pendingUpdateBlock.value.lo1;
105
+ }
106
+
107
+ /**
108
+ * Returns the configured update delay in blocks.
109
+ */
110
+ public get updateDelay(): u64 {
111
+ return this._updateDelay;
112
+ }
113
+
114
+ /**
115
+ * Returns the block number when the pending update can be applied.
116
+ */
117
+ public get updateEffectiveBlock(): u64 {
118
+ const submitBlock = this.pendingUpdateBlock;
119
+ if (submitBlock === 0) return 0;
120
+ return submitBlock + this._updateDelay;
121
+ }
122
+
123
+ /**
124
+ * Returns true if there is a pending update.
125
+ */
126
+ public get hasPendingUpdate(): bool {
127
+ return this.pendingUpdateBlock !== 0;
128
+ }
129
+
130
+ /**
131
+ * Returns true if the pending update can be applied (delay has passed).
132
+ */
133
+ public get canApplyUpdate(): bool {
134
+ if (!this.hasPendingUpdate) return false;
135
+ return Blockchain.block.number >= this.updateEffectiveBlock;
136
+ }
137
+
138
+ /**
139
+ * Attempts to execute an update-related method.
140
+ * Returns the response if the method was handled, or null if not.
141
+ *
142
+ * @param method - The method selector
143
+ * @param calldata - The calldata
144
+ * @returns BytesWriter response if handled, null otherwise
145
+ */
146
+ public override execute(method: Selector, calldata: Calldata): BytesWriter | null {
147
+ switch (method) {
148
+ case UpdatablePlugin.SUBMIT_UPDATE_SELECTOR:
149
+ return this.submitUpdate(calldata);
150
+ case UpdatablePlugin.APPLY_UPDATE_SELECTOR:
151
+ return this.applyUpdate(calldata);
152
+ case UpdatablePlugin.CANCEL_UPDATE_SELECTOR:
153
+ return this.cancelUpdate();
154
+ case UpdatablePlugin.PENDING_UPDATE_SELECTOR:
155
+ return this.getPendingUpdate();
156
+ case UpdatablePlugin.UPDATE_DELAY_SELECTOR:
157
+ return this.getUpdateDelay();
158
+ default:
159
+ return null;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Submits an update for timelock.
165
+ */
166
+ private submitUpdate(calldata: Calldata): BytesWriter {
167
+ this.onlyDeployer();
168
+
169
+ if (this.hasPendingUpdate) {
170
+ throw new Revert('Update already pending. Cancel first.');
171
+ }
172
+
173
+ const sourceAddress = calldata.readAddress();
174
+
175
+ if (!Blockchain.isContract(sourceAddress)) {
176
+ throw new Revert('Source must be a deployed contract');
177
+ }
178
+
179
+ const currentBlock = Blockchain.block.number;
180
+ this._pendingUpdateAddress.value = sourceAddress;
181
+ this._pendingUpdateBlock.value = u256.fromU64(currentBlock);
182
+
183
+ const effectiveBlock = currentBlock + this._updateDelay;
184
+ Blockchain.emit(new UpdateSubmittedEvent(sourceAddress, currentBlock, effectiveBlock));
185
+
186
+ return new BytesWriter(0);
187
+ }
188
+
189
+ /**
190
+ * Applies a pending update after the timelock period has passed.
191
+ * Any remaining calldata after the source address is passed to onUpdate.
192
+ */
193
+ private applyUpdate(calldata: Calldata): BytesWriter {
194
+ this.onlyDeployer();
195
+
196
+ if (!this.hasPendingUpdate) {
197
+ throw new Revert('No pending update');
198
+ }
199
+
200
+ if (!this.canApplyUpdate) {
201
+ throw new Revert('Update delay not elapsed');
202
+ }
203
+
204
+ const sourceAddress = calldata.readAddress();
205
+ const pendingAddress = this._pendingUpdateAddress.value;
206
+
207
+ if (!sourceAddress.equals(pendingAddress)) {
208
+ throw new Revert('Address does not match pending update');
209
+ }
210
+
211
+ // Clear pending state before update
212
+ this._pendingUpdateAddress.value = Address.zero();
213
+ this._pendingUpdateBlock.value = u256.Zero;
214
+
215
+ Blockchain.emit(new UpdateAppliedEvent(sourceAddress, Blockchain.block.number));
216
+
217
+ const updateCalldata = calldata.readBytesWithLength();
218
+
219
+ const writer = new BytesWriter(updateCalldata.byteLength)
220
+ writer.writeBytes(updateCalldata);
221
+
222
+ // Perform update - new bytecode takes effect next block
223
+ Blockchain.updateContractFromExisting(sourceAddress, writer);
224
+
225
+ return new BytesWriter(0);
226
+ }
227
+
228
+ /**
229
+ * Cancels a pending update.
230
+ */
231
+ private cancelUpdate(): BytesWriter {
232
+ this.onlyDeployer();
233
+
234
+ if (!this.hasPendingUpdate) {
235
+ throw new Revert('No pending update');
236
+ }
237
+
238
+ const pendingAddress = this._pendingUpdateAddress.value;
239
+
240
+ this._pendingUpdateAddress.value = Address.zero();
241
+ this._pendingUpdateBlock.value = u256.Zero;
242
+
243
+ Blockchain.emit(new UpdateCancelledEvent(pendingAddress, Blockchain.block.number));
244
+
245
+ return new BytesWriter(0);
246
+ }
247
+
248
+ /**
249
+ * Returns the pending update info.
250
+ */
251
+ private getPendingUpdate(): BytesWriter {
252
+ const response = new BytesWriter(ADDRESS_BYTE_LENGTH + 16);
253
+ response.writeAddress(this._pendingUpdateAddress.value);
254
+ response.writeU64(this.pendingUpdateBlock);
255
+ response.writeU64(this.updateEffectiveBlock);
256
+ return response;
257
+ }
258
+
259
+ /**
260
+ * Returns the update delay.
261
+ */
262
+ private getUpdateDelay(): BytesWriter {
263
+ const response = new BytesWriter(8);
264
+ response.writeU64(this._updateDelay);
265
+ return response;
266
+ }
267
+
268
+ /**
269
+ * Validates that the caller is the contract deployer.
270
+ */
271
+ private onlyDeployer(): void {
272
+ if (Blockchain.contractDeployer !== Blockchain.tx.sender) {
273
+ throw new Revert('Only deployer can call this method');
274
+ }
275
+ }
276
+ }
@@ -58,7 +58,7 @@ export class NetworkManager {
58
58
  case Networks.Regtest:
59
59
  return 'bcrt';
60
60
  case Networks.OpnetTestnet:
61
- return 'opt1';
61
+ return 'opt';
62
62
  default:
63
63
  throw new Revert('Unknown network');
64
64
  }
@@ -1,24 +1,21 @@
1
1
  import { Blockchain } from '../env';
2
2
  import { EMPTY_POINTER, GET_EMPTY_BUFFER } from '../math/bytes';
3
- import { Revert } from '../types/Revert';
4
3
  import { encodePointer } from '../math/abi';
5
4
 
6
5
  @final
7
6
  export class StoredBoolean {
8
7
  private readonly pointerBuffer: Uint8Array;
8
+ private readonly defaultValue: bool;
9
+ private _loaded: bool;
9
10
 
10
11
  constructor(
11
12
  public pointer: u16,
12
13
  defaultValue: bool,
13
14
  ) {
14
15
  this.pointerBuffer = encodePointer(pointer, EMPTY_POINTER, true, 'StoredBoolean');
15
-
16
- const value = GET_EMPTY_BUFFER();
17
- if (defaultValue) {
18
- value[0] = 1;
19
- }
20
-
21
- this._value = value;
16
+ this.defaultValue = defaultValue;
17
+ this._value = GET_EMPTY_BUFFER();
18
+ this._loaded = false;
22
19
  }
23
20
 
24
21
  private _value: Uint8Array;
@@ -32,20 +29,34 @@ export class StoredBoolean {
32
29
 
33
30
  public set value(value: bool) {
34
31
  this._value[0] = value ? 1 : 0;
32
+ this._value[1] = 1; // mark as initialized
33
+ this._loaded = true;
35
34
 
36
35
  Blockchain.setStorageAt(this.pointerBuffer, this._value);
37
36
  }
38
37
 
39
38
  @inline
40
39
  public toUint8Array(): Uint8Array {
41
- if (!this._value) {
42
- throw new Revert(`Not defined.`);
43
- }
40
+ this.ensureValue();
44
41
 
45
42
  return this._value;
46
43
  }
47
44
 
48
45
  private ensureValue(): void {
49
- this._value = Blockchain.getStorageAt(this.pointerBuffer);
46
+ if (this._loaded) {
47
+ return;
48
+ }
49
+
50
+ const stored = Blockchain.getStorageAt(this.pointerBuffer);
51
+ if (stored[1] === 0) {
52
+ this._value[0] = this.defaultValue ? 1 : 0;
53
+ this._value[1] = 1;
54
+
55
+ Blockchain.setStorageAt(this.pointerBuffer, this._value);
56
+ } else {
57
+ this._value = stored;
58
+ }
59
+
60
+ this._loaded = true;
50
61
  }
51
62
  }
@@ -6,7 +6,7 @@ import { MLDSASecurityLevel } from '../env/consensus/MLDSAMetadata';
6
6
  import { ArrayLike } from '../interfaces/as';
7
7
 
8
8
  /**
9
- * Represents a 32-byte address in the OPNet system.
9
+ * Represents a 32-byte address in the OP_NET system.
10
10
  *
11
11
  * This class extends Uint8Array to provide a fixed-size 32-byte address with additional
12
12
  * functionality for quantum-resistant cryptography. The address stores the SHA256 hash
@@ -24,7 +24,7 @@ import { Revert } from './Revert';
24
24
  *
25
25
  * @remarks
26
26
  * This class is marked as @final and cannot be extended. It represents the complete
27
- * address format for OPNet's quantum-resistant transition, supporting both legacy
27
+ * address format for OP_NET's quantum-resistant transition, supporting both legacy
28
28
  * Schnorr signatures and future ML-DSA signatures within the same address structure.
29
29
  *
30
30
  * The tweaked public key enables P2TR (pay-to-taproot) address generation while