@btc-vision/btc-runtime 1.4.6 → 1.5.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/package.json +44 -53
- package/runtime/abort/abort.ts +25 -0
- package/runtime/buffer/BytesReader.ts +171 -140
- package/runtime/buffer/BytesWriter.ts +120 -152
- package/runtime/contracts/DeployableOP_20.ts +29 -15
- package/runtime/contracts/OP_NET.ts +1 -1
- package/runtime/env/BlockchainEnvironment.ts +79 -137
- package/runtime/env/classes/Block.ts +4 -8
- package/runtime/env/classes/Transaction.ts +14 -7
- package/runtime/env/classes/UTXO.ts +4 -2
- package/runtime/env/global.ts +49 -20
- package/runtime/events/predefined/MintEvent.ts +1 -1
- package/runtime/exports/index.ts +29 -8
- package/runtime/generic/AddressMap.ts +7 -5
- package/runtime/generic/Map.ts +32 -2
- package/runtime/generic/MapU256.ts +7 -5
- package/runtime/generic/MapUint8Array.ts +93 -0
- package/runtime/index.ts +4 -12
- package/runtime/math/abi.ts +71 -11
- package/runtime/math/bytes.ts +177 -41
- package/runtime/memory/AddressMemoryMap.ts +22 -19
- package/runtime/memory/FastUint8Array.ts +122 -0
- package/runtime/memory/KeyMerger.ts +25 -23
- package/runtime/memory/MultiAddressMemoryMap.ts +11 -8
- package/runtime/memory/MultiStringMemoryMap.ts +8 -5
- package/runtime/memory/StringMemoryMap.ts +15 -15
- package/runtime/memory/Uint8ArrayMerger.ts +22 -15
- package/runtime/storage/Serializable.ts +19 -20
- package/runtime/storage/StoredAddress.ts +16 -15
- package/runtime/storage/StoredBoolean.ts +26 -21
- package/runtime/storage/StoredString.ts +158 -102
- package/runtime/storage/StoredU256.ts +25 -28
- package/runtime/storage/StoredU64.ts +23 -35
- package/runtime/storage/arrays/StoredAddressArray.ts +88 -179
- package/runtime/storage/arrays/StoredBooleanArray.ts +150 -272
- package/runtime/storage/arrays/StoredPackedArray.ts +313 -0
- package/runtime/storage/arrays/StoredU128Array.ts +38 -373
- package/runtime/storage/arrays/StoredU16Array.ts +34 -418
- package/runtime/storage/arrays/StoredU256Array.ts +21 -346
- package/runtime/storage/arrays/StoredU32Array.ts +37 -438
- package/runtime/storage/arrays/StoredU64Array.ts +66 -0
- package/runtime/storage/arrays/StoredU8Array.ts +29 -451
- package/runtime/types/Address.ts +72 -5
- package/runtime/types/index.ts +1 -4
- package/runtime/utils/encodings.ts +5 -6
- package/runtime/utils/hex.ts +1 -1
- package/runtime/interfaces/DeployContractResponse.ts +0 -12
- package/runtime/math/cyrb53.ts +0 -48
- package/runtime/math/sha256.ts +0 -12
- package/runtime/memory/MemorySlot.ts +0 -1
- package/runtime/memory/MemorySlotPointer.ts +0 -3
- package/runtime/storage/utils/StorageBacked.ts +0 -5
- package/runtime/storage/utils/StorageLayout.ts +0 -7
- package/runtime/storage/utils/StorageSlot.ts +0 -106
- package/runtime/storage/utils/StorageStruct.ts +0 -23
- package/runtime/storage/utils/StorageValue.ts +0 -36
- package/runtime/tests/assert.ts +0 -11
- package/runtime/tests/env.ts +0 -7
- package/runtime/tests/tests.ts +0 -28
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import {
|
|
2
|
+
encodeBasePointer,
|
|
3
|
+
GET_EMPTY_BUFFER,
|
|
4
|
+
readLengthAndStartIndex,
|
|
5
|
+
writeLengthAndStartIndex,
|
|
6
|
+
} from '../../math/bytes';
|
|
7
|
+
import { Blockchain } from '../../env';
|
|
8
|
+
import { Revert } from '../../types/Revert';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Abstract base class for an array of T values that are packed
|
|
12
|
+
* in 32-byte (Uint8Array) "slots" in storage.
|
|
13
|
+
*
|
|
14
|
+
* - Tracks length + startIndex in the first 16 bytes of `lengthPointer`.
|
|
15
|
+
* - Maps each global index to (slotIndex, subIndex).
|
|
16
|
+
* - Child classes define how many T items fit per 32-byte slot
|
|
17
|
+
* and how to pack/unpack them.
|
|
18
|
+
*/
|
|
19
|
+
export abstract class StoredPackedArray<T> {
|
|
20
|
+
/** 32-byte base pointer (used to derive storage keys). */
|
|
21
|
+
protected readonly basePointer: Uint8Array;
|
|
22
|
+
|
|
23
|
+
/** Same pointer used to read/write length + startIndex. */
|
|
24
|
+
protected readonly lengthPointer: Uint8Array;
|
|
25
|
+
|
|
26
|
+
/** Internal length of the array. */
|
|
27
|
+
protected _length: u64 = 0;
|
|
28
|
+
|
|
29
|
+
/** Optional "start index" if needed by your logic. */
|
|
30
|
+
protected _startIndex: u64 = 0;
|
|
31
|
+
|
|
32
|
+
/** Whether length or startIndex changed. */
|
|
33
|
+
protected _isChangedLength: bool = false;
|
|
34
|
+
protected _isChangedStartIndex: bool = false;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A map from slotIndex => the 32-byte slot data in memory.
|
|
38
|
+
* Child classes will parse that 32 bytes into an array of T, or vice versa.
|
|
39
|
+
*/
|
|
40
|
+
protected _slots: Map<u64, Uint8Array> = new Map();
|
|
41
|
+
|
|
42
|
+
/** Track which slotIndexes are changed and need saving. */
|
|
43
|
+
protected _isChanged: Set<u64> = new Set();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Maximum length to prevent unbounded usage.
|
|
47
|
+
* Adjust to fit your constraints (e.g. `u64.MAX_VALUE`).
|
|
48
|
+
*/
|
|
49
|
+
protected readonly MAX_LENGTH: u64 = <u64>(u32.MAX_VALUE - 1);
|
|
50
|
+
|
|
51
|
+
protected constructor(public pointer: u16, public subPointer: Uint8Array) {
|
|
52
|
+
assert(subPointer.length <= 30, `You must pass a 30 bytes sub-pointer. (Array, got ${subPointer.length})`);
|
|
53
|
+
|
|
54
|
+
const basePointer = encodeBasePointer(pointer, subPointer);
|
|
55
|
+
this.lengthPointer = Uint8Array.wrap(basePointer.buffer);
|
|
56
|
+
this.basePointer = basePointer;
|
|
57
|
+
|
|
58
|
+
const storedLenStart = Blockchain.getStorageAt(basePointer);
|
|
59
|
+
const data = readLengthAndStartIndex(storedLenStart);
|
|
60
|
+
|
|
61
|
+
this._length = data[0];
|
|
62
|
+
this._startIndex = data[1];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public get(index: u64): T {
|
|
66
|
+
if (index > this.MAX_LENGTH) {
|
|
67
|
+
throw new Revert('get: index exceeds MAX_LENGTH');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const cap = this.getSlotCapacity();
|
|
71
|
+
const slotIndex = index / cap;
|
|
72
|
+
const subIndex = <u32>(index % cap);
|
|
73
|
+
|
|
74
|
+
// Load the slot if not cached
|
|
75
|
+
const slotData = this.ensureSlot(slotIndex);
|
|
76
|
+
|
|
77
|
+
// Unpack and return the subIndex
|
|
78
|
+
const arr = this.unpackSlot(slotData);
|
|
79
|
+
return arr[subIndex];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public set(index: u64, value: T): void {
|
|
83
|
+
const cap = this.getSlotCapacity();
|
|
84
|
+
const slotIndex = index / cap;
|
|
85
|
+
const subIndex = <u32>(index % cap);
|
|
86
|
+
|
|
87
|
+
let slotData = this.ensureSlot(slotIndex);
|
|
88
|
+
const arr = this.unpackSlot(slotData);
|
|
89
|
+
|
|
90
|
+
if (!this.eq(arr[subIndex], value)) {
|
|
91
|
+
arr[subIndex] = value;
|
|
92
|
+
slotData = this.packSlot(arr);
|
|
93
|
+
this._slots.set(slotIndex, slotData);
|
|
94
|
+
this._isChanged.add(slotIndex);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public push(value: T): void {
|
|
99
|
+
if (this._length >= this.MAX_LENGTH) {
|
|
100
|
+
throw new Revert('push: array has reached MAX_LENGTH');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const newIndex = this._length;
|
|
104
|
+
const cap = this.getSlotCapacity();
|
|
105
|
+
const slotIndex = newIndex / cap;
|
|
106
|
+
const subIndex = <u32>(newIndex % cap);
|
|
107
|
+
|
|
108
|
+
let slotData = this.ensureSlot(slotIndex);
|
|
109
|
+
const arr = this.unpackSlot(slotData);
|
|
110
|
+
|
|
111
|
+
if (!this.eq(arr[subIndex], value)) {
|
|
112
|
+
arr[subIndex] = value;
|
|
113
|
+
slotData = this.packSlot(arr);
|
|
114
|
+
this._slots.set(slotIndex, slotData);
|
|
115
|
+
this._isChanged.add(slotIndex);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this._length += 1;
|
|
119
|
+
this._isChangedLength = true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* "Delete" by zeroing out the element at `index`,
|
|
124
|
+
* but does not reduce the length.
|
|
125
|
+
*/
|
|
126
|
+
public delete(index: u64): void {
|
|
127
|
+
const cap = this.getSlotCapacity();
|
|
128
|
+
const slotIndex = index / cap;
|
|
129
|
+
const subIndex = <u32>(index % cap);
|
|
130
|
+
|
|
131
|
+
let slotData = this.ensureSlot(slotIndex);
|
|
132
|
+
const arr = this.unpackSlot(slotData);
|
|
133
|
+
|
|
134
|
+
const zeroVal = this.zeroValue();
|
|
135
|
+
if (!this.eq(arr[subIndex], zeroVal)) {
|
|
136
|
+
arr[subIndex] = zeroVal;
|
|
137
|
+
slotData = this.packSlot(arr);
|
|
138
|
+
this._slots.set(slotIndex, slotData);
|
|
139
|
+
this._isChanged.add(slotIndex);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Remove the last element by zeroing it and decrementing length by 1.
|
|
145
|
+
*/
|
|
146
|
+
public deleteLast(): void {
|
|
147
|
+
if (this._length == 0) {
|
|
148
|
+
throw new Revert('deleteLast: array is empty');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const lastIndex = this._length - 1;
|
|
152
|
+
this.delete(lastIndex);
|
|
153
|
+
|
|
154
|
+
this._length -= 1;
|
|
155
|
+
this._isChangedLength = true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
public setMultiple(startIndex: u64, values: T[]): void {
|
|
159
|
+
const end = startIndex + <u64>values.length;
|
|
160
|
+
if (end > this._length) {
|
|
161
|
+
throw new Revert('setMultiple: out of range');
|
|
162
|
+
}
|
|
163
|
+
for (let i = 0; i < values.length; i++) {
|
|
164
|
+
this.set(startIndex + <u64>i, values[i]);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// -----------------------------------------------------------
|
|
169
|
+
// Public Array-Like Methods
|
|
170
|
+
// -----------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
public getAll(startIndex: u64, count: u64): T[] {
|
|
173
|
+
if (count > <u64>u32.MAX_VALUE) {
|
|
174
|
+
throw new Revert('getAll: count too large');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const out = new Array<T>(<i32>count);
|
|
178
|
+
for (let i: u64 = 0; i < count; i++) {
|
|
179
|
+
out[<i32>i] = this.get(startIndex + i);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public getLength(): u64 {
|
|
186
|
+
return this._length;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public startingIndex(): u64 {
|
|
190
|
+
return this._startIndex;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
public setStartingIndex(index: u64): void {
|
|
194
|
+
this._startIndex = index;
|
|
195
|
+
this._isChangedStartIndex = true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Return string "[v0, v1, ...]"
|
|
200
|
+
*/
|
|
201
|
+
public toString(): string {
|
|
202
|
+
let s = '[';
|
|
203
|
+
for (let i: u64 = 0; i < this._length; i++) {
|
|
204
|
+
s += `${this.get(i)}`;
|
|
205
|
+
if (i < this._length - 1) {
|
|
206
|
+
s += ', ';
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
s += ']';
|
|
210
|
+
return s;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public save(): void {
|
|
214
|
+
const changed = this._isChanged.values();
|
|
215
|
+
for (let i = 0; i < changed.length; i++) {
|
|
216
|
+
const slotIndex = changed[i];
|
|
217
|
+
const slotData = this._slots.get(slotIndex);
|
|
218
|
+
if (slotData) {
|
|
219
|
+
const ptr = this.calculateStoragePointer(slotIndex);
|
|
220
|
+
Blockchain.setStorageAt(ptr, slotData);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this._isChanged.clear();
|
|
225
|
+
|
|
226
|
+
if (this._isChangedLength || this._isChangedStartIndex) {
|
|
227
|
+
const encoded = writeLengthAndStartIndex(this._length, this._startIndex);
|
|
228
|
+
Blockchain.setStorageAt(this.lengthPointer, encoded);
|
|
229
|
+
this._isChangedLength = false;
|
|
230
|
+
this._isChangedStartIndex = false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public deleteAll(): void {
|
|
235
|
+
const keys = this._slots.keys();
|
|
236
|
+
for (let i = 0; i < keys.length; i++) {
|
|
237
|
+
const slotIndex = keys[i];
|
|
238
|
+
const ptr = this.calculateStoragePointer(slotIndex);
|
|
239
|
+
Blockchain.setStorageAt(ptr, GET_EMPTY_BUFFER()); // 32 bytes of zero
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Reset length + startIndex
|
|
243
|
+
Blockchain.setStorageAt(this.lengthPointer, GET_EMPTY_BUFFER());
|
|
244
|
+
|
|
245
|
+
this._length = 0;
|
|
246
|
+
this._startIndex = 0;
|
|
247
|
+
this._isChangedLength = false;
|
|
248
|
+
this._isChangedStartIndex = false;
|
|
249
|
+
this._slots.clear();
|
|
250
|
+
this._isChanged.clear();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
public reset(): void {
|
|
254
|
+
this._length = 0;
|
|
255
|
+
this._startIndex = 0;
|
|
256
|
+
this._isChangedLength = true;
|
|
257
|
+
this._isChangedStartIndex = true;
|
|
258
|
+
|
|
259
|
+
this._slots.clear();
|
|
260
|
+
this._isChanged.clear();
|
|
261
|
+
|
|
262
|
+
this.save();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Number of T items that fit in one 32-byte slot. */
|
|
266
|
+
protected abstract getSlotCapacity(): u64;
|
|
267
|
+
|
|
268
|
+
/** Return the "zero" value of type T. */
|
|
269
|
+
protected abstract zeroValue(): T;
|
|
270
|
+
|
|
271
|
+
/** Compare two T values for equality. */
|
|
272
|
+
protected abstract eq(a: T, b: T): bool;
|
|
273
|
+
|
|
274
|
+
// -----------------------------------------------------------
|
|
275
|
+
// Persistence (save, reset, etc.)
|
|
276
|
+
// -----------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Pack an array of T (length = getSlotCapacity()) into a 32-byte buffer.
|
|
280
|
+
*/
|
|
281
|
+
protected abstract packSlot(values: T[]): Uint8Array;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Unpack a 32-byte buffer into an array of T (length = getSlotCapacity()).
|
|
285
|
+
*/
|
|
286
|
+
protected abstract unpackSlot(slotData: Uint8Array): T[];
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Calculate storage pointer for each slot index.
|
|
290
|
+
* Typically "basePointer + (slotIndex+1)" in big-endian addition,
|
|
291
|
+
* but you can do your own approach.
|
|
292
|
+
*/
|
|
293
|
+
protected abstract calculateStoragePointer(slotIndex: u64): Uint8Array;
|
|
294
|
+
|
|
295
|
+
// -----------------------------------------------------------
|
|
296
|
+
// Internal Slot-Loading Helpers
|
|
297
|
+
// -----------------------------------------------------------
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Ensure that slotIndex is loaded into _slots. If missing, read from storage.
|
|
301
|
+
*/
|
|
302
|
+
protected ensureSlot(slotIndex: u64): Uint8Array {
|
|
303
|
+
if (!this._slots.has(slotIndex)) {
|
|
304
|
+
const ptr = this.calculateStoragePointer(slotIndex);
|
|
305
|
+
const data = Blockchain.getStorageAt(ptr);
|
|
306
|
+
|
|
307
|
+
// Must be exactly 32 bytes; if it's empty, you get 32 zero bytes from GET_EMPTY_BUFFER()
|
|
308
|
+
this._slots.set(slotIndex, data);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return this._slots.get(slotIndex);
|
|
312
|
+
}
|
|
313
|
+
}
|