@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
|
@@ -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
|
+
}
|
|
@@ -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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/runtime/types/Address.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|