@btc-vision/btc-runtime 1.5.4 → 1.5.7

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 CHANGED
@@ -1,46 +1,46 @@
1
1
  {
2
- "name": "@btc-vision/btc-runtime",
3
- "version": "1.5.4",
4
- "description": "Bitcoin Smart Contract Runtime",
5
- "main": "btc/index.ts",
6
- "scripts": {},
7
- "types": "btc/index.ts",
8
- "keywords": [
9
- "bitcoin",
10
- "smart",
11
- "contract",
12
- "runtime",
13
- "opnet",
14
- "OP_NET"
15
- ],
16
- "homepage": "https://opnet.org",
17
- "author": "BlobMaster41",
18
- "license": "MIT",
19
- "devDependencies": {
20
- "@types/node": "^22.13.10",
21
- "assemblyscript": "^0.27.35"
22
- },
23
- "repository": {
24
- "type": "git",
25
- "url": "https://github.com/btc-vision/btc-runtime"
26
- },
27
- "type": "module",
28
- "files": [
29
- "package.json",
30
- "runtime",
31
- "runtime/*.ts",
32
- "runtime/**/*.ts",
33
- "!**/*.js.map",
34
- "!**/*.tsbuildinfo",
35
- "test/*.ts"
36
- ],
37
- "dependencies": {
38
- "@assemblyscript/loader": "^0.27.35",
39
- "@btc-vision/as-bignum": "^0.0.5",
40
- "@eslint/js": "^9.22.0",
41
- "gulplog": "^2.2.0",
42
- "ts-node": "^10.9.2",
43
- "typescript": "^5.8.2",
44
- "typescript-eslint": "^8.26.1"
45
- }
2
+ "name": "@btc-vision/btc-runtime",
3
+ "version": "1.5.7",
4
+ "description": "Bitcoin Smart Contract Runtime",
5
+ "main": "btc/index.ts",
6
+ "scripts": {},
7
+ "types": "btc/index.ts",
8
+ "keywords": [
9
+ "bitcoin",
10
+ "smart",
11
+ "contract",
12
+ "runtime",
13
+ "opnet",
14
+ "OP_NET"
15
+ ],
16
+ "homepage": "https://opnet.org",
17
+ "author": "BlobMaster41",
18
+ "license": "MIT",
19
+ "devDependencies": {
20
+ "@types/node": "^22.13.10",
21
+ "assemblyscript": "^0.27.35"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/btc-vision/btc-runtime"
26
+ },
27
+ "type": "module",
28
+ "files": [
29
+ "package.json",
30
+ "runtime",
31
+ "runtime/*.ts",
32
+ "runtime/**/*.ts",
33
+ "!**/*.js.map",
34
+ "!**/*.tsbuildinfo",
35
+ "test/*.ts"
36
+ ],
37
+ "dependencies": {
38
+ "@assemblyscript/loader": "^0.27.35",
39
+ "@btc-vision/as-bignum": "^0.0.5",
40
+ "@eslint/js": "^9.22.0",
41
+ "gulplog": "^2.2.0",
42
+ "ts-node": "^10.9.2",
43
+ "typescript": "^5.8.2",
44
+ "typescript-eslint": "^8.26.1"
45
+ }
46
46
  }
@@ -39,7 +39,7 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
39
39
 
40
40
  protected readonly _nonceMap: AddressMemoryMap;
41
41
 
42
- protected constructor(params: OP20InitParameters | null = null) {
42
+ public constructor(params: OP20InitParameters | null = null) {
43
43
  super();
44
44
 
45
45
  // Initialize main storage structures
@@ -3,7 +3,7 @@ import { u256 } from '@btc-vision/as-bignum/assembly';
3
3
  import { OP20InitParameters } from './interfaces/OP20InitParameters';
4
4
 
5
5
  export abstract class OP_20 extends DeployableOP_20 {
6
- protected constructor(maxSupply: u256, decimals: u8, name: string, symbol: string) {
6
+ public constructor(maxSupply: u256, decimals: u8, name: string, symbol: string) {
7
7
  super(new OP20InitParameters(maxSupply, decimals, name, symbol));
8
8
  }
9
9
  }
@@ -25,13 +25,10 @@ import { eqUint, MapUint8Array } from '../generic/MapUint8Array';
25
25
  import { EMPTY_BUFFER } from '../math/bytes';
26
26
  import { Plugin } from '../plugins/Plugin';
27
27
  import { Calldata } from '../types';
28
+ import { Revert } from '../types/Revert';
28
29
 
29
30
  export * from '../env/global';
30
31
 
31
- export function runtimeError(msg: string): Error {
32
- return new Error(`RuntimeException: ${msg}`);
33
- }
34
-
35
32
  @final
36
33
  export class BlockchainEnvironment {
37
34
  private static readonly MAX_U16: u16 = 65535;
@@ -47,7 +44,7 @@ export class BlockchainEnvironment {
47
44
  @inline
48
45
  public get block(): Block {
49
46
  if (!this._block) {
50
- throw this.error('Block is required');
47
+ throw new Revert('Block is required');
51
48
  }
52
49
 
53
50
  return this._block as Block;
@@ -58,7 +55,7 @@ export class BlockchainEnvironment {
58
55
  @inline
59
56
  public get tx(): Transaction {
60
57
  if (!this._tx) {
61
- throw this.error('Transaction is required');
58
+ throw new Revert('Transaction is required');
62
59
  }
63
60
 
64
61
  return this._tx as Transaction;
@@ -80,7 +77,7 @@ export class BlockchainEnvironment {
80
77
 
81
78
  public get nextPointer(): u16 {
82
79
  if (this._nextPointer === BlockchainEnvironment.MAX_U16) {
83
- throw this.error(`Out of storage pointer.`);
80
+ throw new Revert(`Out of storage pointer.`);
84
81
  }
85
82
 
86
83
  this._nextPointer += 1;
@@ -92,7 +89,7 @@ export class BlockchainEnvironment {
92
89
 
93
90
  public get contractDeployer(): Address {
94
91
  if (!this._contractDeployer) {
95
- throw this.error('Deployer is required');
92
+ throw new Revert('Deployer is required');
96
93
  }
97
94
 
98
95
  return this._contractDeployer as Address;
@@ -102,7 +99,7 @@ export class BlockchainEnvironment {
102
99
 
103
100
  public get contractAddress(): Address {
104
101
  if (!this._contractAddress) {
105
- throw this.error('Contract address is required');
102
+ throw new Revert('Contract address is required');
106
103
  }
107
104
 
108
105
  return this._contractAddress as Address;
@@ -148,6 +145,7 @@ export class BlockchainEnvironment {
148
145
  const blockHash = reader.readBytes(32);
149
146
  const blockNumber = reader.readU64();
150
147
  const blockMedianTime = reader.readU64();
148
+ const txId = reader.readBytes(32);
151
149
  const txHash = reader.readBytes(32);
152
150
  const contractAddress = reader.readAddress();
153
151
  const contractDeployer = reader.readAddress();
@@ -157,6 +155,7 @@ export class BlockchainEnvironment {
157
155
  this._tx = new Transaction(
158
156
  caller,
159
157
  origin,
158
+ txId,
160
159
  txHash,
161
160
  );
162
161
 
@@ -169,12 +168,8 @@ export class BlockchainEnvironment {
169
168
  }
170
169
 
171
170
  public call(destinationContract: Address, calldata: BytesWriter): BytesReader {
172
- if (destinationContract === this.contractAddress) {
173
- throw this.error('Cannot call self');
174
- }
175
-
176
171
  if (!destinationContract) {
177
- throw this.error('Destination contract is required');
172
+ throw new Revert('Destination contract is required');
178
173
  }
179
174
 
180
175
  const resultLengthBuffer = new ArrayBuffer(32);
@@ -232,7 +227,7 @@ export class BlockchainEnvironment {
232
227
  );
233
228
 
234
229
  if (status !== 0) {
235
- throw this.error('Failed to deploy contract');
230
+ throw new Revert('Failed to deploy contract');
236
231
  }
237
232
 
238
233
  const contractAddressReader = new BytesReader(Uint8Array.wrap(resultAddressBuffer));
@@ -281,7 +276,7 @@ export class BlockchainEnvironment {
281
276
 
282
277
  private createContractIfNotExists(): void {
283
278
  if (!this._contract) {
284
- throw this.error('Contract is required');
279
+ throw new Revert('Contract is required');
285
280
  }
286
281
 
287
282
  if (!this._selfContract) {
@@ -289,10 +284,6 @@ export class BlockchainEnvironment {
289
284
  }
290
285
  }
291
286
 
292
- private error(msg: string): Error {
293
- return runtimeError(msg);
294
- }
295
-
296
287
  private _internalSetStorageAt(pointerHash: Uint8Array, value: Uint8Array): void {
297
288
  this.storage.set(pointerHash, value);
298
289
 
@@ -9,6 +9,7 @@ export class Transaction {
9
9
  public constructor(
10
10
  public readonly sender: Address, // "immediate caller"
11
11
  public readonly origin: Address, // "leftmost thing in the call chain"
12
+ public readonly txId: Uint8Array,
12
13
  public readonly hash: Uint8Array,
13
14
  ) {
14
15
  }
@@ -5,7 +5,7 @@ import { Selector } from '../math/abi';
5
5
  import { Calldata } from '../types';
6
6
  import { env_exit, getCalldata, getEnvironmentVariables } from '../env/global';
7
7
 
8
- const ENVIRONMENT_VARIABLES_BYTE_LENGTH: u32 = 208;
8
+ const ENVIRONMENT_VARIABLES_BYTE_LENGTH: u32 = 240;
9
9
 
10
10
  export function execute(calldataLength: u32): u32 {
11
11
  const environmentVariablesBuffer = new ArrayBuffer(ENVIRONMENT_VARIABLES_BYTE_LENGTH);
@@ -171,23 +171,7 @@ export function encodeBasePointer(pointer: u16, subPointer: Uint8Array): Uint8Ar
171
171
 
172
172
  @inline
173
173
  export function bigEndianAdd(base: Uint8Array, increment: u64): Uint8Array {
174
- const out = new Uint8Array(32);
174
+ const add = u64ToBE32Bytes(increment);
175
175
 
176
- // Copy the base pointer
177
- for (let i = 0; i < 32; i++) {
178
- out[i] = base[i];
179
- }
180
-
181
- // If this is truly big-endian, out[0] is the MSB, out[31] is the LSB.
182
- // So to add `increment`, we start from out[31] backward.
183
- let carry: u64 = increment;
184
- for (let i = 31; i >= 0; i--) {
185
- const sum = <u64>out[i] + (carry & 0xFF);
186
- out[i] = <u8>(sum & 0xFF);
187
- carry = sum >> 8;
188
- if (carry == 0 || i == 0) {
189
- break;
190
- }
191
- }
192
- return out;
176
+ return addUint8ArraysBE(base, add);
193
177
  }
@@ -1,6 +1,12 @@
1
1
  import { BytesWriter } from '../../buffer/BytesWriter';
2
2
  import { Blockchain } from '../../env';
3
- import { addUint8ArraysBE, encodeBasePointer, readLengthAndStartIndex, u64ToBE32Bytes } from '../../math/bytes';
3
+ import {
4
+ addUint8ArraysBE,
5
+ bigEndianAdd,
6
+ encodeBasePointer,
7
+ readLengthAndStartIndex,
8
+ u64ToBE32Bytes,
9
+ } from '../../math/bytes';
4
10
  import { Address } from '../../types/Address';
5
11
  import { Revert } from '../../types/Revert';
6
12
 
@@ -39,7 +45,7 @@ export class StoredAddressArray {
39
45
 
40
46
  const basePointer = encodeBasePointer(pointer, subPointer);
41
47
  this.lengthPointer = Uint8Array.wrap(basePointer.buffer);
42
- this.baseU256Pointer = basePointer;
48
+ this.baseU256Pointer = bigEndianAdd(basePointer, 1);
43
49
 
44
50
  const storedLenStart = Blockchain.getStorageAt(basePointer);
45
51
  const data = readLengthAndStartIndex(storedLenStart);
@@ -48,13 +54,21 @@ export class StoredAddressArray {
48
54
  this._startIndex = data[1];
49
55
  }
50
56
 
57
+ @inline
58
+ public has(index: u64): bool {
59
+ return index < this._length;
60
+ }
61
+
51
62
  /** Get an element by its global index. */
52
63
  @inline
64
+ @operator('[]')
53
65
  public get(index: u64): Address {
54
- if (index > this.MAX_LENGTH) {
55
- throw new Revert('Operation failed: Index exceeds maximum allowed value.');
66
+ if (index >= this._length) {
67
+ throw new Revert('get: index out of range (address array)');
56
68
  }
57
- const slotIndex: u32 = <u32>index;
69
+
70
+ const physicalIndex = (this._startIndex + index) % this.MAX_LENGTH;
71
+ const slotIndex: u32 = <u32>physicalIndex;
58
72
  this.ensureValues(slotIndex);
59
73
 
60
74
  return this._values.get(slotIndex);
@@ -62,11 +76,14 @@ export class StoredAddressArray {
62
76
 
63
77
  /** Set an element by its global index. */
64
78
  @inline
79
+ @operator('[]=')
65
80
  public set(index: u64, value: Address): void {
66
- if (index > this.MAX_LENGTH) {
67
- throw new Revert('Set failed: Index exceeds maximum allowed value.');
81
+ if (index >= this._length) {
82
+ throw new Revert('set: index out of range (address array)');
68
83
  }
69
- const slotIndex: u32 = <u32>index;
84
+
85
+ const physicalIndex = (this._startIndex + index) % this.MAX_LENGTH;
86
+ const slotIndex: u32 = <u32>physicalIndex;
70
87
  this.ensureValues(slotIndex);
71
88
 
72
89
  const currentValue = this._values.get(slotIndex);
@@ -76,33 +93,16 @@ export class StoredAddressArray {
76
93
  }
77
94
  }
78
95
 
79
- /** Find the first index containing `value`. Returns -1 if not found. */
80
- @inline
81
- public indexOf(value: Address): i64 {
82
- for (let i: u64 = 0; i < this._length; i++) {
83
- if (this.get(i) == value) {
84
- return i64(i);
85
- }
86
- }
87
- return -1;
88
- }
89
-
90
- /** Check if the array contains `value`. */
91
- @inline
92
- public contains(value: Address): boolean {
93
- return this.indexOf(value) !== -1;
94
- }
95
-
96
96
  /** Append an address at the end of the array. */
97
+ @inline
97
98
  public push(value: Address): void {
98
99
  if (this._length >= this.MAX_LENGTH) {
99
- throw new Revert('Push failed: Array has reached its maximum length.');
100
+ throw new Revert('push: array reached maximum length (address array)');
100
101
  }
101
102
 
102
- const newIndex: u64 = this._length;
103
- const wrappedIndex: u64 =
104
- newIndex < this.MAX_LENGTH ? newIndex : newIndex % this.MAX_LENGTH;
105
- const slotIndex: u32 = <u32>wrappedIndex;
103
+ const newLogicalIndex: u64 = this._length;
104
+ const physicalIndex: u64 = (this._startIndex + newLogicalIndex) % this.MAX_LENGTH;
105
+ const slotIndex: u32 = <u32>physicalIndex;
106
106
 
107
107
  this.ensureValues(slotIndex);
108
108
  this._values.set(slotIndex, value);
@@ -115,11 +115,12 @@ export class StoredAddressArray {
115
115
  /** Delete the last element. */
116
116
  public deleteLast(): void {
117
117
  if (this._length === 0) {
118
- throw new Revert('Delete failed: Array is empty.');
118
+ throw new Revert('deleteLast: array is empty (address array)');
119
119
  }
120
120
 
121
- const lastIndex: u64 = this._length - 1;
122
- const slotIndex: u32 = <u32>(this._startIndex + lastIndex);
121
+ const lastLogicalIndex: u64 = this._length - 1;
122
+ const physicalIndex: u64 = (this._startIndex + lastLogicalIndex) % this.MAX_LENGTH;
123
+ const slotIndex: u32 = <u32>physicalIndex;
123
124
  this.ensureValues(slotIndex);
124
125
 
125
126
  const currentValue = this._values.get(slotIndex);
@@ -139,12 +140,14 @@ export class StoredAddressArray {
139
140
  }
140
141
 
141
142
  /** Delete a specific element by setting it to `defaultValue`. */
143
+ @inline
142
144
  public delete(index: u64): void {
143
145
  if (index > this.MAX_LENGTH) {
144
- throw new Revert('Operation failed: Index exceeds maximum allowed value.');
146
+ throw new Revert('delete: index out of range (address array)');
145
147
  }
146
148
 
147
- const slotIndex: u32 = <u32>index;
149
+ const physicalIndex: u64 = (this._startIndex + index) % this.MAX_LENGTH;
150
+ const slotIndex: u32 = <u32>physicalIndex;
148
151
  this.ensureValues(slotIndex);
149
152
 
150
153
  const currentValue = this._values.get(slotIndex);
@@ -159,6 +162,7 @@ export class StoredAddressArray {
159
162
  * - Store any changed slotIndex -> Address
160
163
  * - Store updated length and startIndex if changed
161
164
  */
165
+ @inline
162
166
  public save(): void {
163
167
  // 1) Save changed slots
164
168
  const changed = this._isChanged.values();
@@ -185,7 +189,6 @@ export class StoredAddressArray {
185
189
 
186
190
  /** Clear entire array content from storage, reset length and startIndex. */
187
191
  public deleteAll(): void {
188
- // Clear all loaded slots
189
192
  const keys = this._values.keys();
190
193
  for (let i = 0; i < keys.length; i++) {
191
194
  const slotIndex = keys[i];
@@ -193,14 +196,13 @@ export class StoredAddressArray {
193
196
  Blockchain.setStorageAt(storagePointer, this.defaultValue);
194
197
  }
195
198
 
196
- // Reset length and startIndex in storage
197
- Blockchain.setStorageAt(this.lengthPointer, new Uint8Array(0)); // or a 16-byte zero array if preferred
199
+ Blockchain.setStorageAt(this.lengthPointer, new Uint8Array(32));
200
+
198
201
  this._length = 0;
199
202
  this._startIndex = 0;
200
203
  this._isChangedLength = false;
201
204
  this._isChangedStartIndex = false;
202
205
 
203
- // Clear internal caches
204
206
  this._values.clear();
205
207
  this._isChanged.clear();
206
208
  }
@@ -217,7 +219,7 @@ export class StoredAddressArray {
217
219
  @inline
218
220
  public getAll(startIndex: u32, count: u32): Address[] {
219
221
  if (startIndex + count > this._length) {
220
- throw new Revert('Requested range exceeds array length');
222
+ throw new Revert('getAll: index out of range (address array)');
221
223
  }
222
224
 
223
225
  const result = new Array<Address>(count);
@@ -249,6 +251,10 @@ export class StoredAddressArray {
249
251
  this._startIndex = 0;
250
252
  this._isChangedLength = true;
251
253
  this._isChangedStartIndex = true;
254
+
255
+ this._values.clear();
256
+ this._isChanged.clear();
257
+
252
258
  this.save();
253
259
  }
254
260
 
@@ -284,8 +290,8 @@ export class StoredAddressArray {
284
290
  * Compute a 32-byte storage pointer = basePointer + (slotIndex + 1) big-endian.
285
291
  */
286
292
  private calculateStoragePointer(slotIndex: u64): Uint8Array {
287
- // Convert (slotIndex + 1) to a 32-byte big-endian offset
288
- const offset = u64ToBE32Bytes(slotIndex + 1);
293
+ // Convert (slotIndex) to a 32-byte big-endian offset
294
+ const offset = u64ToBE32Bytes(slotIndex);
289
295
 
290
296
  return addUint8ArraysBE(this.baseU256Pointer, offset);
291
297
  }
@@ -3,6 +3,7 @@ import { Blockchain } from '../../env';
3
3
  import { Revert } from '../../types/Revert';
4
4
  import {
5
5
  addUint8ArraysBE,
6
+ bigEndianAdd,
6
7
  encodeBasePointer,
7
8
  GET_EMPTY_BUFFER,
8
9
  getBit,
@@ -51,7 +52,7 @@ export class StoredBooleanArray {
51
52
 
52
53
  const basePointer = encodeBasePointer(pointer, subPtr);
53
54
  this.lengthPointer = Uint8Array.wrap(basePointer.buffer);
54
- this.basePointer = basePointer;
55
+ this.basePointer = bigEndianAdd(basePointer, 1);
55
56
 
56
57
  const storedLenStart = Blockchain.getStorageAt(basePointer);
57
58
  const data = readLengthAndStartIndex(storedLenStart);
@@ -62,16 +63,28 @@ export class StoredBooleanArray {
62
63
 
63
64
  // -------------- Public Accessors -------------- //
64
65
 
66
+ @inline
67
+ public has(index: u64): bool {
68
+ return index < this._length;
69
+ }
70
+
65
71
  /**
66
72
  * Retrieve boolean at `index`.
67
73
  */
74
+ @operator('[]')
68
75
  @inline
69
76
  public get(index: u64): bool {
70
- if (index > this.MAX_LENGTH) {
71
- throw new Revert('get: Index exceeds maximum allowed value.');
77
+ if (index >= this._length) {
78
+ throw new Revert(`get: index out of range (${index} >= ${this._length}, boolean array)`);
72
79
  }
73
- const slotIndex = index / 256; // which 32-byte slot
74
- const bitIndex = <u16>(index % 256);
80
+
81
+ const effectiveIndex = this._startIndex + index;
82
+ const wrappedIndex = effectiveIndex < this.MAX_LENGTH
83
+ ? effectiveIndex
84
+ : effectiveIndex % this.MAX_LENGTH;
85
+
86
+ const slotIndex = wrappedIndex / 256;
87
+ const bitIndex = <u16>(wrappedIndex % 256);
75
88
 
76
89
  this.ensureSlotLoaded(slotIndex);
77
90
 
@@ -82,13 +95,20 @@ export class StoredBooleanArray {
82
95
  /**
83
96
  * Set boolean at `index`.
84
97
  */
98
+ @operator('[]=')
85
99
  @inline
86
100
  public set(index: u64, value: bool): void {
87
- if (index > this.MAX_LENGTH) {
88
- throw new Revert('set: Index exceeds maximum allowed value.');
101
+ if (index >= this._length) {
102
+ throw new Revert(`set: index out of range (${index} >= ${this._length}, boolean array)`);
89
103
  }
90
- const slotIndex = index / 256;
91
- const bitIndex = <u16>(index % 256);
104
+
105
+ const effectiveIndex = this._startIndex + index;
106
+ const wrappedIndex = effectiveIndex < this.MAX_LENGTH
107
+ ? effectiveIndex
108
+ : effectiveIndex % this.MAX_LENGTH;
109
+
110
+ const slotIndex = wrappedIndex / 256;
111
+ const bitIndex = <u16>(wrappedIndex % 256);
92
112
 
93
113
  this.ensureSlotLoaded(slotIndex);
94
114
 
@@ -108,7 +128,7 @@ export class StoredBooleanArray {
108
128
  @inline
109
129
  public push(value: bool): void {
110
130
  if (this._length >= this.MAX_LENGTH) {
111
- throw new Revert('push: Reached max allowed length');
131
+ throw new Revert('push: reached max allowed length (boolean array)');
112
132
  }
113
133
 
114
134
  const newIndex = this._length;
@@ -137,11 +157,17 @@ export class StoredBooleanArray {
137
157
  */
138
158
  @inline
139
159
  public delete(index: u64): void {
140
- if (index > this.MAX_LENGTH) {
141
- throw new Revert('delete: Index exceeds maximum allowed value.');
160
+ if (index >= this._length) {
161
+ throw new Revert('delete: index out of range (boolean array)');
142
162
  }
143
- const slotIndex = index / 256;
144
- const bitIndex = <u16>(index % 256);
163
+
164
+ const effectiveIndex = this._startIndex + index;
165
+ const wrappedIndex = effectiveIndex < this.MAX_LENGTH
166
+ ? effectiveIndex
167
+ : effectiveIndex % this.MAX_LENGTH;
168
+
169
+ const slotIndex = wrappedIndex / 256;
170
+ const bitIndex = <u16>(wrappedIndex % 256);
145
171
 
146
172
  this.ensureSlotLoaded(slotIndex);
147
173
 
@@ -161,8 +187,9 @@ export class StoredBooleanArray {
161
187
  @inline
162
188
  public deleteLast(): void {
163
189
  if (this._length === 0) {
164
- throw new Revert('deleteLast: Array is empty');
190
+ throw new Revert('deleteLast: array is empty');
165
191
  }
192
+
166
193
  const lastIndex = this._length - 1;
167
194
  this.delete(lastIndex);
168
195
 
@@ -200,11 +227,10 @@ export class StoredBooleanArray {
200
227
  }
201
228
 
202
229
  /**
203
- * Delete all slots in storage (that are loaded) + reset length + startIndex.
230
+ * Delete all slots in storage (that are loaded) + reset length + _startIndex.
204
231
  */
205
232
  @inline
206
233
  public deleteAll(): void {
207
- // clear all loaded slots
208
234
  const keys = this._values.keys();
209
235
  const zeroArr = GET_EMPTY_BUFFER();
210
236
  for (let i = 0; i < keys.length; i++) {
@@ -213,15 +239,14 @@ export class StoredBooleanArray {
213
239
  Blockchain.setStorageAt(storagePointer, zeroArr);
214
240
  }
215
241
 
216
- // also reset length + startIndex in storage
217
242
  const writer = new BytesWriter(32);
218
243
  Blockchain.setStorageAt(this.lengthPointer, writer.getBuffer());
219
244
 
220
- // reset in memory
221
245
  this._length = 0;
222
246
  this._startIndex = 0;
223
247
  this._isChangedLength = false;
224
248
  this._isChangedStartIndex = false;
249
+
225
250
  this._values.clear();
226
251
  this._isChanged.clear();
227
252
  }
@@ -240,18 +265,20 @@ export class StoredBooleanArray {
240
265
  * Retrieve a batch of bools.
241
266
  */
242
267
  @inline
243
- public getAll(startIndex: u64, count: u64): bool[] {
244
- if (startIndex + count > this._length) {
245
- throw new Revert('getAll: range exceeds array length');
268
+ public getAll(start: u64, count: u64): bool[] {
269
+ if (start + count > this._length) {
270
+ throw new Revert('getAll: range exceeds array length (boolean array)');
246
271
  }
272
+
247
273
  if (count > u64(u32.MAX_VALUE)) {
248
- throw new Revert('getAll: range exceeds max allowed');
274
+ throw new Revert('getAll: range exceeds max allowed (boolean array)');
249
275
  }
250
276
 
251
277
  const result = new Array<bool>(<i32>count);
252
278
  for (let i: u64 = 0; i < count; i++) {
253
- result[<i32>i] = this.get(startIndex + i);
279
+ result[<i32>i] = this.get(start + i);
254
280
  }
281
+
255
282
  return result;
256
283
  }
257
284
 
@@ -295,6 +322,12 @@ export class StoredBooleanArray {
295
322
  return this._length;
296
323
  }
297
324
 
325
+ @inline
326
+ public setStartingIndex(index: u64): void {
327
+ this._startIndex = index;
328
+ this._isChangedStartIndex = true;
329
+ }
330
+
298
331
  /**
299
332
  * Current starting index for the array.
300
333
  */
@@ -303,7 +336,6 @@ export class StoredBooleanArray {
303
336
  return this._startIndex;
304
337
  }
305
338
 
306
-
307
339
  /**
308
340
  * Ensure the 32-byte slot for `slotIndex` is loaded into _values.
309
341
  */
@@ -319,7 +351,7 @@ export class StoredBooleanArray {
319
351
  * Convert `slotIndex` -> pointer = basePointer + (slotIndex + 1), as big-endian addition.
320
352
  */
321
353
  private calculateStoragePointer(slotIndex: u64): Uint8Array {
322
- const offset = u64ToBE32Bytes(slotIndex + 1);
354
+ const offset = u64ToBE32Bytes(slotIndex);
323
355
  return addUint8ArraysBE(this.basePointer, offset);
324
356
  }
325
357
  }
@@ -1,4 +1,5 @@
1
1
  import {
2
+ bigEndianAdd,
2
3
  encodeBasePointer,
3
4
  GET_EMPTY_BUFFER,
4
5
  readLengthAndStartIndex,
@@ -48,12 +49,12 @@ export abstract class StoredPackedArray<T> {
48
49
  */
49
50
  protected readonly MAX_LENGTH: u64 = <u64>(u32.MAX_VALUE - 1);
50
51
 
51
- protected constructor(public pointer: u16, public subPointer: Uint8Array) {
52
+ protected constructor(public pointer: u16, public subPointer: Uint8Array, protected defaultValue: T) {
52
53
  assert(subPointer.length <= 30, `You must pass a 30 bytes sub-pointer. (Array, got ${subPointer.length})`);
53
54
 
54
55
  const basePointer = encodeBasePointer(pointer, subPointer);
55
56
  this.lengthPointer = Uint8Array.wrap(basePointer.buffer);
56
- this.basePointer = basePointer;
57
+ this.basePointer = bigEndianAdd(basePointer, 1);
57
58
 
58
59
  const storedLenStart = Blockchain.getStorageAt(basePointer);
59
60
  const data = readLengthAndStartIndex(storedLenStart);
@@ -62,24 +63,70 @@ export abstract class StoredPackedArray<T> {
62
63
  this._startIndex = data[1];
63
64
  }
64
65
 
66
+ @inline
67
+ @operator('[]')
65
68
  public get(index: u64): T {
69
+ // max length used on purpose to prevent unbounded usage
66
70
  if (index > this.MAX_LENGTH) {
67
- throw new Revert('get: index exceeds MAX_LENGTH');
71
+ throw new Revert('get: out of range');
72
+ }
73
+
74
+ const realIndex = (this._startIndex + index) % this.MAX_LENGTH;
75
+ const cap = this.getSlotCapacity();
76
+ const slotIndex = realIndex / cap;
77
+ const subIndex = <u32>(realIndex % cap);
78
+
79
+ const slotData = this.ensureSlot(slotIndex);
80
+ const arr = this.unpackSlot(slotData);
81
+
82
+ return arr[subIndex];
83
+ }
84
+
85
+ @inline
86
+ public get_physical(index: u64): T {
87
+ if (index > this.MAX_LENGTH) {
88
+ throw new Revert('get: index exceeds MAX_LENGTH (packed array)');
68
89
  }
69
90
 
70
91
  const cap = this.getSlotCapacity();
71
92
  const slotIndex = index / cap;
72
93
  const subIndex = <u32>(index % cap);
73
94
 
74
- // Load the slot if not cached
75
95
  const slotData = this.ensureSlot(slotIndex);
76
-
77
- // Unpack and return the subIndex
78
96
  const arr = this.unpackSlot(slotData);
97
+
79
98
  return arr[subIndex];
80
99
  }
81
100
 
101
+ @inline
102
+ @operator('[]=')
82
103
  public set(index: u64, value: T): void {
104
+ if (index > this.MAX_LENGTH) {
105
+ throw new Revert('set: index exceeds MAX_LENGTH (packed array)');
106
+ }
107
+
108
+ const realIndex = (this._startIndex + index) % this.MAX_LENGTH;
109
+ const cap = this.getSlotCapacity();
110
+ const slotIndex = realIndex / cap;
111
+ const subIndex = <u32>(realIndex % cap);
112
+
113
+ let slotData = this.ensureSlot(slotIndex);
114
+ const arr = this.unpackSlot(slotData);
115
+
116
+ if (!this.eq(arr[subIndex], value)) {
117
+ arr[subIndex] = value;
118
+ slotData = this.packSlot(arr);
119
+ this._slots.set(slotIndex, slotData);
120
+ this._isChanged.add(slotIndex);
121
+ }
122
+ }
123
+
124
+ @inline
125
+ public set_physical(index: u64, value: T): void {
126
+ if (index > this.MAX_LENGTH) {
127
+ throw new Revert('set: index exceeds MAX_LENGTH (packed array)');
128
+ }
129
+
83
130
  const cap = this.getSlotCapacity();
84
131
  const slotIndex = index / cap;
85
132
  const subIndex = <u32>(index % cap);
@@ -95,15 +142,16 @@ export abstract class StoredPackedArray<T> {
95
142
  }
96
143
  }
97
144
 
98
- public push(value: T): void {
145
+ @inline
146
+ public push(value: T, isPhysical: bool = false): void {
99
147
  if (this._length >= this.MAX_LENGTH) {
100
148
  throw new Revert('push: array has reached MAX_LENGTH');
101
149
  }
102
150
 
103
- const newIndex = this._length;
151
+ const realIndex = ((isPhysical ? 0 : this._startIndex) + this._length) % this.MAX_LENGTH;
104
152
  const cap = this.getSlotCapacity();
105
- const slotIndex = newIndex / cap;
106
- const subIndex = <u32>(newIndex % cap);
153
+ const slotIndex = realIndex / cap;
154
+ const subIndex = <u32>(realIndex % cap);
107
155
 
108
156
  let slotData = this.ensureSlot(slotIndex);
109
157
  const arr = this.unpackSlot(slotData);
@@ -119,11 +167,70 @@ export abstract class StoredPackedArray<T> {
119
167
  this._isChangedLength = true;
120
168
  }
121
169
 
170
+ /**
171
+ * Remove the first element by zeroing it and shifting all other elements.
172
+ */
173
+ @inline
174
+ public shift(): T {
175
+ if (this._length == 0) {
176
+ throw new Revert('shift: array is empty (packed array)');
177
+ }
178
+
179
+ const newIndex = this._startIndex;
180
+ const cap = this.getSlotCapacity();
181
+ const slotIndex = newIndex / cap;
182
+ const subIndex = <u32>(newIndex % cap);
183
+
184
+ let slotData = this.ensureSlot(slotIndex);
185
+
186
+ const arr = this.unpackSlot(slotData);
187
+ const currentData = arr[subIndex];
188
+
189
+ if (!this.eq(currentData, this.defaultValue)) {
190
+ arr[subIndex] = this.defaultValue;
191
+ slotData = this.packSlot(arr);
192
+
193
+ this._slots.set(slotIndex, slotData);
194
+ this._isChanged.add(slotIndex);
195
+ }
196
+
197
+ this._length -= 1;
198
+ this._startIndex += 1;
199
+ this._isChangedLength = true;
200
+ this._isChangedStartIndex = true;
201
+
202
+ return currentData;
203
+ }
204
+
122
205
  /**
123
206
  * "Delete" by zeroing out the element at `index`,
124
207
  * but does not reduce the length.
125
208
  */
209
+ @inline
126
210
  public delete(index: u64): void {
211
+ const realIndex = (this._startIndex + index) % this.MAX_LENGTH;
212
+ const cap = this.getSlotCapacity();
213
+ const slotIndex = realIndex / cap;
214
+ const subIndex = <u32>(realIndex % cap);
215
+
216
+ let slotData = this.ensureSlot(slotIndex);
217
+ const arr = this.unpackSlot(slotData);
218
+
219
+ const zeroVal = this.zeroValue();
220
+ if (!this.eq(arr[subIndex], zeroVal)) {
221
+ arr[subIndex] = zeroVal;
222
+ slotData = this.packSlot(arr);
223
+ this._slots.set(slotIndex, slotData);
224
+ this._isChanged.add(slotIndex);
225
+ }
226
+ }
227
+
228
+ /**
229
+ * "Delete" by zeroing out the element at `index`,
230
+ * but does not reduce the length.
231
+ */
232
+ @inline
233
+ public delete_physical(index: u64): void {
127
234
  const cap = this.getSlotCapacity();
128
235
  const slotIndex = index / cap;
129
236
  const subIndex = <u32>(index % cap);
@@ -143,9 +250,10 @@ export abstract class StoredPackedArray<T> {
143
250
  /**
144
251
  * Remove the last element by zeroing it and decrementing length by 1.
145
252
  */
253
+ @inline
146
254
  public deleteLast(): void {
147
255
  if (this._length == 0) {
148
- throw new Revert('deleteLast: array is empty');
256
+ throw new Revert('deleteLast: array is empty (packed array)');
149
257
  }
150
258
 
151
259
  const lastIndex = this._length - 1;
@@ -155,11 +263,13 @@ export abstract class StoredPackedArray<T> {
155
263
  this._isChangedLength = true;
156
264
  }
157
265
 
266
+ @inline
158
267
  public setMultiple(startIndex: u64, values: T[]): void {
159
268
  const end = startIndex + <u64>values.length;
160
269
  if (end > this._length) {
161
- throw new Revert('setMultiple: out of range');
270
+ throw new Revert('setMultiple: out of range (packed array)');
162
271
  }
272
+
163
273
  for (let i = 0; i < values.length; i++) {
164
274
  this.set(startIndex + <u64>i, values[i]);
165
275
  }
@@ -168,10 +278,10 @@ export abstract class StoredPackedArray<T> {
168
278
  // -----------------------------------------------------------
169
279
  // Public Array-Like Methods
170
280
  // -----------------------------------------------------------
171
-
281
+ @inline
172
282
  public getAll(startIndex: u64, count: u64): T[] {
173
283
  if (count > <u64>u32.MAX_VALUE) {
174
- throw new Revert('getAll: count too large');
284
+ throw new Revert('getAll: count too large (packed array)');
175
285
  }
176
286
 
177
287
  const out = new Array<T>(<i32>count);
@@ -182,14 +292,17 @@ export abstract class StoredPackedArray<T> {
182
292
  return out;
183
293
  }
184
294
 
295
+ @inline
185
296
  public getLength(): u64 {
186
297
  return this._length;
187
298
  }
188
299
 
300
+ @inline
189
301
  public startingIndex(): u64 {
190
302
  return this._startIndex;
191
303
  }
192
304
 
305
+ @inline
193
306
  public setStartingIndex(index: u64): void {
194
307
  this._startIndex = index;
195
308
  this._isChangedStartIndex = true;
@@ -250,6 +363,9 @@ export abstract class StoredPackedArray<T> {
250
363
  this._isChanged.clear();
251
364
  }
252
365
 
366
+ /**
367
+ * Reset the array to its initial state.
368
+ */
253
369
  public reset(): void {
254
370
  this._length = 0;
255
371
  this._startIndex = 0;
@@ -10,7 +10,7 @@ import { bigEndianAdd } from '../../math/bytes';
10
10
  @final
11
11
  export class StoredU128Array extends StoredPackedArray<u128> {
12
12
  public constructor(pointer: u16, subPointer: Uint8Array) {
13
- super(pointer, subPointer);
13
+ super(pointer, subPointer, u128.Zero);
14
14
  }
15
15
 
16
16
  protected getSlotCapacity(): u64 {
@@ -59,6 +59,6 @@ export class StoredU128Array extends StoredPackedArray<u128> {
59
59
  }
60
60
 
61
61
  protected calculateStoragePointer(slotIndex: u64): Uint8Array {
62
- return bigEndianAdd(this.basePointer, slotIndex + 1);
62
+ return bigEndianAdd(this.basePointer, slotIndex);
63
63
  }
64
64
  }
@@ -7,7 +7,7 @@ import { bigEndianAdd } from '../../math/bytes';
7
7
  @final
8
8
  export class StoredU16Array extends StoredPackedArray<u16> {
9
9
  public constructor(pointer: u16, subPointer: Uint8Array) {
10
- super(pointer, subPointer);
10
+ super(pointer, subPointer, 0);
11
11
  }
12
12
 
13
13
  protected getSlotCapacity(): u64 {
@@ -53,6 +53,6 @@ export class StoredU16Array extends StoredPackedArray<u16> {
53
53
  }
54
54
 
55
55
  protected calculateStoragePointer(slotIndex: u64): Uint8Array {
56
- return bigEndianAdd(this.basePointer, slotIndex + 1);
56
+ return bigEndianAdd(this.basePointer, slotIndex);
57
57
  }
58
58
  }
@@ -9,7 +9,7 @@ import { bigEndianAdd } from '../../math/bytes';
9
9
  @final
10
10
  export class StoredU256Array extends StoredPackedArray<u256> {
11
11
  public constructor(pointer: u16, subPointer: Uint8Array) {
12
- super(pointer, subPointer);
12
+ super(pointer, subPointer, u256.Zero);
13
13
  }
14
14
 
15
15
  protected getSlotCapacity(): u64 {
@@ -34,6 +34,6 @@ export class StoredU256Array extends StoredPackedArray<u256> {
34
34
  }
35
35
 
36
36
  protected calculateStoragePointer(slotIndex: u64): Uint8Array {
37
- return bigEndianAdd(this.basePointer, slotIndex + 1);
37
+ return bigEndianAdd(this.basePointer, slotIndex);
38
38
  }
39
39
  }
@@ -8,7 +8,7 @@ import { bigEndianAdd } from '../../math/bytes';
8
8
  @final
9
9
  export class StoredU32Array extends StoredPackedArray<u32> {
10
10
  public constructor(pointer: u16, subPointer: Uint8Array) {
11
- super(pointer, subPointer);
11
+ super(pointer, subPointer, 0);
12
12
  }
13
13
 
14
14
  protected getSlotCapacity(): u64 {
@@ -52,6 +52,6 @@ export class StoredU32Array extends StoredPackedArray<u32> {
52
52
  }
53
53
 
54
54
  protected calculateStoragePointer(slotIndex: u64): Uint8Array {
55
- return bigEndianAdd(this.basePointer, slotIndex + 1);
55
+ return bigEndianAdd(this.basePointer, slotIndex);
56
56
  }
57
57
  }
@@ -8,7 +8,7 @@ import { bigEndianAdd } from '../../math/bytes';
8
8
  @final
9
9
  export class StoredU64Array extends StoredPackedArray<u64> {
10
10
  public constructor(pointer: u16, subPointer: Uint8Array) {
11
- super(pointer, subPointer);
11
+ super(pointer, subPointer, 0);
12
12
  }
13
13
 
14
14
  protected getSlotCapacity(): u64 {
@@ -61,6 +61,6 @@ export class StoredU64Array extends StoredPackedArray<u64> {
61
61
  }
62
62
 
63
63
  protected calculateStoragePointer(slotIndex: u64): Uint8Array {
64
- return bigEndianAdd(this.basePointer, slotIndex + 1);
64
+ return bigEndianAdd(this.basePointer, slotIndex);
65
65
  }
66
66
  }
@@ -8,7 +8,7 @@ import { bigEndianAdd } from '../../math/bytes';
8
8
  @final
9
9
  export class StoredU8Array extends StoredPackedArray<u8> {
10
10
  public constructor(pointer: u16, subPointer: Uint8Array) {
11
- super(pointer, subPointer);
11
+ super(pointer, subPointer, 0);
12
12
  }
13
13
 
14
14
  protected getSlotCapacity(): u64 {
@@ -43,6 +43,6 @@ export class StoredU8Array extends StoredPackedArray<u8> {
43
43
 
44
44
  protected calculateStoragePointer(slotIndex: u64): Uint8Array {
45
45
  // basePointer + (slotIndex+1) in big-endian
46
- return bigEndianAdd(this.basePointer, slotIndex + 1);
46
+ return bigEndianAdd(this.basePointer, slotIndex);
47
47
  }
48
48
  }