@btc-vision/btc-runtime 1.9.1 → 1.9.3

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 (31) hide show
  1. package/package.json +13 -4
  2. package/runtime/constants/Exports.ts +86 -0
  3. package/runtime/contracts/OP1155.ts +1042 -0
  4. package/runtime/contracts/OP20.ts +56 -37
  5. package/runtime/contracts/OP721.ts +882 -0
  6. package/runtime/contracts/OP_NET.ts +5 -0
  7. package/runtime/contracts/ReentrancyGuard.ts +136 -0
  8. package/runtime/contracts/interfaces/IOP1155.ts +33 -0
  9. package/runtime/contracts/interfaces/IOP721.ts +29 -0
  10. package/runtime/contracts/interfaces/OP1155InitParameters.ts +11 -0
  11. package/runtime/contracts/interfaces/OP721InitParameters.ts +15 -0
  12. package/runtime/env/BlockchainEnvironment.ts +42 -3
  13. package/runtime/events/predefined/ApprovedForAll.ts +16 -0
  14. package/runtime/events/predefined/TransferredBatchEvent.ts +35 -0
  15. package/runtime/events/predefined/TransferredSingleEvent.ts +18 -0
  16. package/runtime/events/predefined/URIEvent.ts +23 -0
  17. package/runtime/events/predefined/index.ts +4 -0
  18. package/runtime/index.ts +9 -0
  19. package/runtime/math/abi.ts +23 -0
  20. package/runtime/math/bytes.ts +4 -0
  21. package/runtime/memory/AddressMemoryMap.ts +20 -0
  22. package/runtime/nested/storage/StorageMap.ts +3 -23
  23. package/runtime/nested/storage/StorageSet.ts +6 -3
  24. package/runtime/script/Script.ts +1 -1
  25. package/runtime/secp256k1/ECPoint.ts +3 -3
  26. package/runtime/shared-libraries/OP20Utils.ts +1 -2
  27. package/runtime/storage/AdvancedStoredString.ts +8 -189
  28. package/runtime/storage/BaseStoredString.ts +206 -0
  29. package/runtime/storage/StoredString.ts +15 -194
  30. package/runtime/storage/arrays/StoredPackedArray.ts +13 -4
  31. package/runtime/types/SafeMath.ts +125 -94
@@ -41,7 +41,7 @@ export class ECPoint {
41
41
  // x3 = λ^2 - 2x mod P
42
42
  // y3 = λ*(x - x3) - y mod P
43
43
  // ----------------------------
44
- static double(p: ECPoint): ECPoint {
44
+ public static double(p: ECPoint): ECPoint {
45
45
  // If y=0, return infinity
46
46
  if (p.y == u256.Zero) {
47
47
  return new ECPoint(u256.Zero, u256.Zero); // "Point at infinity" convention
@@ -80,7 +80,7 @@ export class ECPoint {
80
80
  // x3 = λ^2 - x1 - x2 mod P
81
81
  // y3 = λ*(x1 - x3) - y1 mod P
82
82
  // ----------------------------
83
- static add(p: ECPoint, q: ECPoint): ECPoint {
83
+ public static add(p: ECPoint, q: ECPoint): ECPoint {
84
84
  // 1) Check for infinity cases
85
85
  const isPInfinity = p.x.isZero() && p.y.isZero();
86
86
  const isQInfinity = q.x.isZero() && q.y.isZero();
@@ -122,7 +122,7 @@ export class ECPoint {
122
122
  // Scalar Multiplication: k*P
123
123
  // Double-and-add approach
124
124
  // ----------------------------
125
- static scalarMultiply(p: ECPoint, k: u256): ECPoint {
125
+ public static scalarMultiply(p: ECPoint, k: u256): ECPoint {
126
126
  let result = new ECPoint(u256.Zero, u256.Zero); // ∞
127
127
  let addend = p;
128
128
  const two = u256.fromU64(2);
@@ -16,7 +16,6 @@ export class OP20Utils {
16
16
  calldata.writeAddress(owner);
17
17
 
18
18
  const response = Blockchain.call(token, calldata);
19
-
20
- return response.readU256();
19
+ return response.data.readU256();
21
20
  }
22
21
  }
@@ -1,199 +1,18 @@
1
- import { Blockchain } from '../env';
2
- import { encodePointer } from '../math/abi';
3
- import { bigEndianAdd } from '../math/bytes';
4
- import { Revert } from '../types/Revert';
5
-
6
- const MAX_LENGTH: u32 = 256;
1
+ import { BaseStoredString } from './BaseStoredString';
7
2
 
8
3
  /**
9
4
  * @class AdvancedStoredString
10
5
  * @description
11
- * Stores a string in a sequence of 32-byte storage slots, in UTF-8 format:
12
- * - Slot 0: first 4 bytes = length (big-endian), next 28 bytes = partial data
13
- * - Slot N>0: 32 bytes of data each
14
- *
15
- * The maximum is 65,535 bytes in UTF-8 form (not necessarily the same as code points).
6
+ * Stores a string with a directly provided subPointer.
7
+ * Maximum length: 256 bytes (configurable)
16
8
  */
17
9
  @final
18
- export class AdvancedStoredString {
19
- constructor(
20
- public pointer: u16,
21
- private readonly subPointer: Uint8Array,
22
- ) {}
23
-
24
- private _value: string = '';
25
-
26
- /**
27
- * Cached string value. If `_value` is empty, we call `load()` on first access.
28
- */
29
- public get value(): string {
30
- if (!this._value) {
31
- this.load();
32
- }
33
- return this._value;
34
- }
35
-
36
- public set value(v: string) {
37
- this._value = v;
38
-
39
- this.save();
40
- }
41
-
42
- /**
43
- * Derives a 32-byte pointer for the given chunkIndex and performs big-endian addition.
44
- * chunkIndex=0 => header slot, 1 => second slot, etc.
45
- */
46
- private getPointer(chunkIndex: u64): Uint8Array {
47
- const base = encodePointer(this.pointer, this.subPointer, true, 'AdvancedStoredString');
48
- return bigEndianAdd(base, chunkIndex);
10
+ export class AdvancedStoredString extends BaseStoredString {
11
+ constructor(pointer: u16, subPointer: Uint8Array, maxLength: u32 = 256) {
12
+ super(pointer, subPointer, maxLength);
49
13
  }
50
14
 
51
- /**
52
- * Reads the first slot and returns the stored byte length (big-endian).
53
- * Returns 0 if the slot is all zero.
54
- */
55
- private getStoredLength(): u32 {
56
- const headerSlot = Blockchain.getStorageAt(this.getPointer(0));
57
- const b0 = <u32>headerSlot[0];
58
- const b1 = <u32>headerSlot[1];
59
- const b2 = <u32>headerSlot[2];
60
- const b3 = <u32>headerSlot[3];
61
- return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
62
- }
63
-
64
- /**
65
- * Clears old data from storage. Based on `oldLength`, determines how many slots
66
- * were used, and writes zeroed 32-byte arrays to each.
67
- */
68
- private clearOldStorage(oldLength: u32): void {
69
- if (oldLength == 0) {
70
- return;
71
- }
72
-
73
- // We always use at least 1 slot (the header slot).
74
- let chunkCount: u64 = 1;
75
-
76
- // In the header slot, we can store up to 28 bytes of data.
77
- const remaining = oldLength > 28 ? oldLength - 28 : 0;
78
- if (remaining > 0) {
79
- // Each additional chunk is 32 bytes.
80
- // Use integer math ceiling: (remaining + 32 - 1) / 32
81
- chunkCount += (remaining + 32 - 1) / 32;
82
- }
83
-
84
- // Zero out each previously used slot
85
- for (let i: u64 = 0; i < chunkCount; i++) {
86
- Blockchain.setStorageAt(this.getPointer(i), new Uint8Array(32));
87
- }
88
- }
89
-
90
- /**
91
- * Saves the current string to storage in UTF-8 form.
92
- */
93
- private save(): void {
94
- // 1) Clear old data
95
- const oldLen = this.getStoredLength();
96
- this.clearOldStorage(oldLen);
97
-
98
- // 2) Encode new string as UTF-8
99
- const utf8Data = String.UTF8.encode(this._value, false);
100
- const length = <u32>utf8Data.byteLength;
101
-
102
- // Enforce max length
103
- if (length > MAX_LENGTH) {
104
- throw new Revert(`StoredString: value is too long (max=${MAX_LENGTH})`);
105
- }
106
-
107
- // 3) If new string is empty, just store a zeroed header and return
108
- if (length == 0) {
109
- // A zeroed 32-byte array => indicates length=0
110
- Blockchain.setStorageAt(this.getPointer(0), new Uint8Array(32));
111
- return;
112
- }
113
-
114
- // 4) Write the first slot: length + up to 28 bytes
115
- let remaining: u32 = length;
116
- let offset: u32 = 0;
117
- const firstSlot = new Uint8Array(32);
118
- firstSlot[0] = <u8>((length >> 24) & 0xff);
119
- firstSlot[1] = <u8>((length >> 16) & 0xff);
120
- firstSlot[2] = <u8>((length >> 8) & 0xff);
121
- firstSlot[3] = <u8>(length & 0xff);
122
-
123
- const bytes = Uint8Array.wrap(utf8Data);
124
- const firstChunkSize = remaining < 28 ? remaining : 28;
125
- for (let i: u32 = 0; i < firstChunkSize; i++) {
126
- firstSlot[4 + i] = bytes[i];
127
- }
128
- Blockchain.setStorageAt(this.getPointer(0), firstSlot);
129
-
130
- remaining -= firstChunkSize;
131
- offset += firstChunkSize;
132
-
133
- // 5) Write subsequent slots (32 bytes each)
134
- let chunkIndex: u64 = 1;
135
- while (remaining > 0) {
136
- const slotData = new Uint8Array(32);
137
- const chunkSize = remaining < u32(32) ? remaining : u32(32);
138
- for (let i: u32 = 0; i < chunkSize; i++) {
139
- slotData[i] = bytes[offset + i];
140
- }
141
- Blockchain.setStorageAt(this.getPointer(chunkIndex), slotData);
142
-
143
- remaining -= chunkSize;
144
- offset += chunkSize;
145
- chunkIndex++;
146
- }
147
- }
148
-
149
- /**
150
- * Loads the string from storage by reading the stored byte length, then decoding
151
- * the corresponding UTF-8 data from the slots.
152
- */
153
- private load(): void {
154
- // Read the header slot first
155
- const headerSlot = Blockchain.getStorageAt(this.getPointer(0));
156
-
157
- // Parse the big-endian length
158
- const b0 = <u32>headerSlot[0];
159
- const b1 = <u32>headerSlot[1];
160
- const b2 = <u32>headerSlot[2];
161
- const b3 = <u32>headerSlot[3];
162
- const length: u32 = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
163
-
164
- // If length=0, then the string is empty
165
- if (length == 0) {
166
- this._value = '';
167
- return;
168
- }
169
-
170
- // Read the UTF-8 bytes from storage
171
- let remaining: u32 = length;
172
- let offset: u32 = 0;
173
- const out = new Uint8Array(length);
174
-
175
- // First slot can hold up to 28 bytes after the length
176
- const firstChunkSize = remaining < 28 ? remaining : 28;
177
- for (let i: u32 = 0; i < firstChunkSize; i++) {
178
- out[i] = headerSlot[4 + i];
179
- }
180
- remaining -= firstChunkSize;
181
- offset += firstChunkSize;
182
-
183
- // Read the subsequent slots of 32 bytes each
184
- let chunkIndex: u64 = 1;
185
- while (remaining > 0) {
186
- const slotData = Blockchain.getStorageAt(this.getPointer(chunkIndex));
187
- const chunkSize = remaining < 32 ? remaining : 32;
188
- for (let i: u32 = 0; i < chunkSize; i++) {
189
- out[offset + i] = slotData[i];
190
- }
191
- remaining -= chunkSize;
192
- offset += chunkSize;
193
- chunkIndex++;
194
- }
195
-
196
- // Decode UTF-8 into a normal string
197
- this._value = String.UTF8.decode(out.buffer, false);
15
+ protected getClassName(): string {
16
+ return 'AdvancedStoredString';
198
17
  }
199
18
  }
@@ -0,0 +1,206 @@
1
+ import { Blockchain } from '../env';
2
+ import { encodePointer } from '../math/abi';
3
+ import { bigEndianAdd } from '../math/bytes';
4
+ import { Revert } from '../types/Revert';
5
+
6
+ /**
7
+ * @class BaseStoredString
8
+ * @description
9
+ * Base class for storing a string in a sequence of 32-byte storage slots, in UTF-8 format:
10
+ * - Slot 0: first 4 bytes = length (big-endian), next 28 bytes = partial data
11
+ * - Slot N>0: 32 bytes of data each
12
+ *
13
+ * The maximum is configurable via MAX_LENGTH parameter (default: 256).
14
+ */
15
+ export abstract class BaseStoredString {
16
+ protected readonly MAX_LENGTH: u32;
17
+ protected readonly subPointer: Uint8Array;
18
+
19
+ protected constructor(
20
+ public pointer: u16,
21
+ subPointer: Uint8Array,
22
+ maxLength: u32 = 256,
23
+ ) {
24
+ this.subPointer = subPointer;
25
+ this.MAX_LENGTH = maxLength;
26
+ }
27
+
28
+ protected _value: string = '';
29
+
30
+ /**
31
+ * Cached string value. If `_value` is empty, we call `load()` on first access.
32
+ */
33
+ public get value(): string {
34
+ if (!this._value) {
35
+ this.load();
36
+ }
37
+ return this._value;
38
+ }
39
+
40
+ public set value(v: string) {
41
+ this._value = v;
42
+ this.save();
43
+ }
44
+
45
+ /**
46
+ * Derives a 32-byte pointer for the given chunkIndex and performs big-endian addition.
47
+ * chunkIndex=0 => header slot, 1 => second slot, etc.
48
+ */
49
+ protected getPointer(chunkIndex: u64): Uint8Array {
50
+ const className = this.getClassName();
51
+ const base = encodePointer(this.pointer, this.subPointer, true, className);
52
+ return bigEndianAdd(base, chunkIndex);
53
+ }
54
+
55
+ /**
56
+ * Returns the class name for error messages - override in subclasses
57
+ */
58
+ protected abstract getClassName(): string;
59
+
60
+ /**
61
+ * Reads the first slot and returns the stored byte length (big-endian).
62
+ * Returns 0 if the slot is all zero.
63
+ */
64
+ protected getStoredLength(): u32 {
65
+ const headerSlot = Blockchain.getStorageAt(this.getPointer(0));
66
+ const b0 = <u32>headerSlot[0];
67
+ const b1 = <u32>headerSlot[1];
68
+ const b2 = <u32>headerSlot[2];
69
+ const b3 = <u32>headerSlot[3];
70
+ return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
71
+ }
72
+
73
+ /**
74
+ * Clears old data from storage. Based on `oldLength`, determines how many slots
75
+ * were used, and writes zeroed 32-byte arrays to each.
76
+ */
77
+ protected clearOldStorage(oldLength: u32): void {
78
+ if (oldLength == 0) {
79
+ return;
80
+ }
81
+
82
+ // We always use at least 1 slot (the header slot).
83
+ let chunkCount: u64 = 1;
84
+
85
+ // In the header slot, we can store up to 28 bytes of data.
86
+ const remaining = oldLength > 28 ? oldLength - 28 : 0;
87
+ if (remaining > 0) {
88
+ // Each additional chunk is 32 bytes.
89
+ // Use integer math ceiling: (remaining + 32 - 1) / 32
90
+ chunkCount += (remaining + 32 - 1) / 32;
91
+ }
92
+
93
+ // Zero out each previously used slot
94
+ for (let i: u64 = 0; i < chunkCount; i++) {
95
+ Blockchain.setStorageAt(this.getPointer(i), new Uint8Array(32));
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Saves the current string to storage in UTF-8 form.
101
+ */
102
+ protected save(): void {
103
+ const oldLen = this.getStoredLength();
104
+ this.clearOldStorage(oldLen);
105
+
106
+ const utf8Data = String.UTF8.encode(this._value, false);
107
+ const length = <u32>utf8Data.byteLength;
108
+
109
+ // Enforce max length
110
+ if (length > this.MAX_LENGTH) {
111
+ throw new Revert(`${this.getClassName()}: value is too long (max=${this.MAX_LENGTH})`);
112
+ }
113
+
114
+ // If new string is empty, just store a zeroed header and return
115
+ if (length == 0) {
116
+ // A zeroed 32-byte array => indicates length=0
117
+ Blockchain.setStorageAt(this.getPointer(0), new Uint8Array(32));
118
+ return;
119
+ }
120
+
121
+ // Write the first slot: length + up to 28 bytes
122
+ let remaining: u32 = length;
123
+ let offset: u32 = 0;
124
+ const firstSlot = new Uint8Array(32);
125
+ firstSlot[0] = <u8>((length >> 24) & 0xff);
126
+ firstSlot[1] = <u8>((length >> 16) & 0xff);
127
+ firstSlot[2] = <u8>((length >> 8) & 0xff);
128
+ firstSlot[3] = <u8>(length & 0xff);
129
+
130
+ const bytes = Uint8Array.wrap(utf8Data);
131
+ const firstChunkSize = remaining < 28 ? remaining : 28;
132
+ for (let i: u32 = 0; i < firstChunkSize; i++) {
133
+ firstSlot[4 + i] = bytes[i];
134
+ }
135
+ Blockchain.setStorageAt(this.getPointer(0), firstSlot);
136
+
137
+ remaining -= firstChunkSize;
138
+ offset += firstChunkSize;
139
+
140
+ // Write subsequent slots (32 bytes each)
141
+ let chunkIndex: u64 = 1;
142
+ while (remaining > 0) {
143
+ const slotData = new Uint8Array(32);
144
+ const chunkSize = remaining < u32(32) ? remaining : u32(32);
145
+ for (let i: u32 = 0; i < chunkSize; i++) {
146
+ slotData[i] = bytes[offset + i];
147
+ }
148
+ Blockchain.setStorageAt(this.getPointer(chunkIndex), slotData);
149
+
150
+ remaining -= chunkSize;
151
+ offset += chunkSize;
152
+ chunkIndex++;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Loads the string from storage by reading the stored byte length, then decoding
158
+ * the corresponding UTF-8 data from the slots.
159
+ */
160
+ protected load(): void {
161
+ // Read the header slot first
162
+ const headerSlot = Blockchain.getStorageAt(this.getPointer(0));
163
+
164
+ // Parse the big-endian length
165
+ const b0 = <u32>headerSlot[0];
166
+ const b1 = <u32>headerSlot[1];
167
+ const b2 = <u32>headerSlot[2];
168
+ const b3 = <u32>headerSlot[3];
169
+ const length: u32 = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
170
+
171
+ // If length=0, then the string is empty
172
+ if (length == 0) {
173
+ this._value = '';
174
+ return;
175
+ }
176
+
177
+ // Read the UTF-8 bytes from storage
178
+ let remaining: u32 = length;
179
+ let offset: u32 = 0;
180
+ const out = new Uint8Array(length);
181
+
182
+ // First slot can hold up to 28 bytes after the length
183
+ const firstChunkSize = remaining < 28 ? remaining : 28;
184
+ for (let i: u32 = 0; i < firstChunkSize; i++) {
185
+ out[i] = headerSlot[4 + i];
186
+ }
187
+ remaining -= firstChunkSize;
188
+ offset += firstChunkSize;
189
+
190
+ // Read the subsequent slots of 32 bytes each
191
+ let chunkIndex: u64 = 1;
192
+ while (remaining > 0) {
193
+ const slotData = Blockchain.getStorageAt(this.getPointer(chunkIndex));
194
+ const chunkSize = remaining < 32 ? remaining : 32;
195
+ for (let i: u32 = 0; i < chunkSize; i++) {
196
+ out[offset + i] = slotData[i];
197
+ }
198
+ remaining -= chunkSize;
199
+ offset += chunkSize;
200
+ chunkIndex++;
201
+ }
202
+
203
+ // Decode UTF-8 into a normal string
204
+ this._value = String.UTF8.decode(out.buffer, false);
205
+ }
206
+ }
@@ -1,207 +1,28 @@
1
- import { Blockchain } from '../env';
2
- import { encodePointer } from '../math/abi';
3
- import { bigEndianAdd } from '../math/bytes';
4
- import { Revert } from '../types/Revert';
5
- import { u256 } from '@btc-vision/as-bignum/assembly';
1
+ import { BaseStoredString } from './BaseStoredString';
6
2
  import { SafeMath } from '../types/SafeMath';
7
-
8
- const MAX_LENGTH = <u32>u16.MAX_VALUE;
9
- const MAX_LENGTH_U256 = u256.fromU32(<u32>MAX_LENGTH);
3
+ import { u256 } from '@btc-vision/as-bignum/assembly';
10
4
 
11
5
  /**
12
6
  * @class StoredString
13
7
  * @description
14
- * Stores a string in a sequence of 32-byte storage slots, in UTF-8 format:
15
- * - Slot 0: first 4 bytes = length (big-endian), next 28 bytes = partial data
16
- * - Slot N>0: 32 bytes of data each
17
- *
18
- * The maximum is 65,535 bytes in UTF-8 form (not necessarily the same as code points).
8
+ * Stores a string with an index-based subPointer calculation.
9
+ * Maximum length: 65,535 bytes (u16.MAX_VALUE)
19
10
  */
20
11
  @final
21
- export class StoredString {
22
- private readonly subPointer: Uint8Array;
23
-
24
- constructor(
25
- public pointer: u16,
26
- index: u64 = 0,
27
- ) {
28
- const indexed = SafeMath.mul(u256.fromU64(index), MAX_LENGTH_U256);
29
- this.subPointer = indexed.toUint8Array(true).slice(0, 30);
30
- }
31
-
32
- private _value: string = '';
33
-
34
- /**
35
- * Cached string value. If `_value` is empty, we call `load()` on first access.
36
- */
37
- public get value(): string {
38
- if (!this._value) {
39
- this.load();
40
- }
41
- return this._value;
42
- }
43
-
44
- public set value(v: string) {
45
- this._value = v;
46
-
47
- this.save();
48
- }
49
-
50
- /**
51
- * Derives a 32-byte pointer for the given chunkIndex and performs big-endian addition.
52
- * chunkIndex=0 => header slot, 1 => second slot, etc.
53
- */
54
- private getPointer(chunkIndex: u64): Uint8Array {
55
- const base = encodePointer(this.pointer, this.subPointer, true, 'StoredString');
56
- return bigEndianAdd(base, chunkIndex);
57
- }
58
-
59
- /**
60
- * Reads the first slot and returns the stored byte length (big-endian).
61
- * Returns 0 if the slot is all zero.
62
- */
63
- private getStoredLength(): u32 {
64
- const headerSlot = Blockchain.getStorageAt(this.getPointer(0));
65
- const b0 = <u32>headerSlot[0];
66
- const b1 = <u32>headerSlot[1];
67
- const b2 = <u32>headerSlot[2];
68
- const b3 = <u32>headerSlot[3];
69
- return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
70
- }
71
-
72
- /**
73
- * Clears old data from storage. Based on `oldLength`, determines how many slots
74
- * were used, and writes zeroed 32-byte arrays to each.
75
- */
76
- private clearOldStorage(oldLength: u32): void {
77
- if (oldLength == 0) {
78
- return;
79
- }
80
-
81
- // We always use at least 1 slot (the header slot).
82
- let chunkCount: u64 = 1;
12
+ export class StoredString extends BaseStoredString {
13
+ private static readonly DEFAULT_MAX_LENGTH: u32 = <u32>u16.MAX_VALUE;
14
+ private static readonly MAX_LENGTH_U256: u256 = u256.fromU32(
15
+ <u32>StoredString.DEFAULT_MAX_LENGTH,
16
+ );
83
17
 
84
- // In the header slot, we can store up to 28 bytes of data.
85
- const remaining = oldLength > 28 ? oldLength - 28 : 0;
86
- if (remaining > 0) {
87
- // Each additional chunk is 32 bytes.
88
- // Use integer math ceiling: (remaining + 32 - 1) / 32
89
- chunkCount += (remaining + 32 - 1) / 32;
90
- }
18
+ constructor(pointer: u16, index: u64 = 0) {
19
+ const indexed = SafeMath.mul(u256.fromU64(index), StoredString.MAX_LENGTH_U256);
20
+ const subPointer = indexed.toUint8Array(true).slice(2, 32);
91
21
 
92
- // Zero out each previously used slot
93
- for (let i: u64 = 0; i < chunkCount; i++) {
94
- Blockchain.setStorageAt(this.getPointer(i), new Uint8Array(32));
95
- }
22
+ super(pointer, subPointer, StoredString.DEFAULT_MAX_LENGTH);
96
23
  }
97
24
 
98
- /**
99
- * Saves the current string to storage in UTF-8 form.
100
- */
101
- private save(): void {
102
- // 1) Clear old data
103
- const oldLen = this.getStoredLength();
104
- this.clearOldStorage(oldLen);
105
-
106
- // 2) Encode new string as UTF-8
107
- const utf8Data = String.UTF8.encode(this._value, false);
108
- const length = <u32>utf8Data.byteLength;
109
-
110
- // Enforce max length
111
- if (length > MAX_LENGTH) {
112
- throw new Revert(`StoredString: value is too long (max=${MAX_LENGTH})`);
113
- }
114
-
115
- // 3) If new string is empty, just store a zeroed header and return
116
- if (length == 0) {
117
- // A zeroed 32-byte array => indicates length=0
118
- Blockchain.setStorageAt(this.getPointer(0), new Uint8Array(32));
119
- return;
120
- }
121
-
122
- // 4) Write the first slot: length + up to 28 bytes
123
- let remaining: u32 = length;
124
- let offset: u32 = 0;
125
- const firstSlot = new Uint8Array(32);
126
- firstSlot[0] = <u8>((length >> 24) & 0xff);
127
- firstSlot[1] = <u8>((length >> 16) & 0xff);
128
- firstSlot[2] = <u8>((length >> 8) & 0xff);
129
- firstSlot[3] = <u8>(length & 0xff);
130
-
131
- const bytes = Uint8Array.wrap(utf8Data);
132
- const firstChunkSize = remaining < 28 ? remaining : 28;
133
- for (let i: u32 = 0; i < firstChunkSize; i++) {
134
- firstSlot[4 + i] = bytes[i];
135
- }
136
- Blockchain.setStorageAt(this.getPointer(0), firstSlot);
137
-
138
- remaining -= firstChunkSize;
139
- offset += firstChunkSize;
140
-
141
- // 5) Write subsequent slots (32 bytes each)
142
- let chunkIndex: u64 = 1;
143
- while (remaining > 0) {
144
- const slotData = new Uint8Array(32);
145
- const chunkSize = remaining < u32(32) ? remaining : u32(32);
146
- for (let i: u32 = 0; i < chunkSize; i++) {
147
- slotData[i] = bytes[offset + i];
148
- }
149
- Blockchain.setStorageAt(this.getPointer(chunkIndex), slotData);
150
-
151
- remaining -= chunkSize;
152
- offset += chunkSize;
153
- chunkIndex++;
154
- }
155
- }
156
-
157
- /**
158
- * Loads the string from storage by reading the stored byte length, then decoding
159
- * the corresponding UTF-8 data from the slots.
160
- */
161
- private load(): void {
162
- // Read the header slot first
163
- const headerSlot = Blockchain.getStorageAt(this.getPointer(0));
164
-
165
- // Parse the big-endian length
166
- const b0 = <u32>headerSlot[0];
167
- const b1 = <u32>headerSlot[1];
168
- const b2 = <u32>headerSlot[2];
169
- const b3 = <u32>headerSlot[3];
170
- const length: u32 = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
171
-
172
- // If length=0, then the string is empty
173
- if (length == 0) {
174
- this._value = '';
175
- return;
176
- }
177
-
178
- // Read the UTF-8 bytes from storage
179
- let remaining: u32 = length;
180
- let offset: u32 = 0;
181
- const out = new Uint8Array(length);
182
-
183
- // First slot can hold up to 28 bytes after the length
184
- const firstChunkSize = remaining < 28 ? remaining : 28;
185
- for (let i: u32 = 0; i < firstChunkSize; i++) {
186
- out[i] = headerSlot[4 + i];
187
- }
188
- remaining -= firstChunkSize;
189
- offset += firstChunkSize;
190
-
191
- // Read the subsequent slots of 32 bytes each
192
- let chunkIndex: u64 = 1;
193
- while (remaining > 0) {
194
- const slotData = Blockchain.getStorageAt(this.getPointer(chunkIndex));
195
- const chunkSize = remaining < 32 ? remaining : 32;
196
- for (let i: u32 = 0; i < chunkSize; i++) {
197
- out[offset + i] = slotData[i];
198
- }
199
- remaining -= chunkSize;
200
- offset += chunkSize;
201
- chunkIndex++;
202
- }
203
-
204
- // Decode UTF-8 into a normal string
205
- this._value = String.UTF8.decode(out.buffer, false);
25
+ protected getClassName(): string {
26
+ return 'StoredString';
206
27
  }
207
28
  }