@btc-vision/btc-runtime 1.6.1 → 1.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btc-vision/btc-runtime",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "Bitcoin Smart Contract Runtime",
5
5
  "main": "btc/index.ts",
6
6
  "scripts": {},
@@ -1,5 +1,4 @@
1
1
  import { i128, u128, u256 } from '@btc-vision/as-bignum/assembly';
2
- import { TransactionInput, TransactionOutput } from '../env/classes/UTXO';
3
2
  import { AddressMap } from '../generic/AddressMap';
4
3
  import { Selector } from '../math/abi';
5
4
  import { Address } from '../types/Address';
@@ -242,7 +241,7 @@ export class BytesReader {
242
241
  return addr;
243
242
  }
244
243
 
245
- // ------------------- Arrays ------------------- //
244
+ // ------------------- Arrays ------------------- //
246
245
 
247
246
  /**
248
247
  * The AS writer does `writeU32(length)` for U256 arrays, so we read a u32.
@@ -330,34 +329,6 @@ export class BytesReader {
330
329
  return result;
331
330
  }
332
331
 
333
- public readTransactionInputs(): TransactionInput[] {
334
- const length = this.readU16();
335
- const result = new Array<TransactionInput>(length);
336
-
337
- for (let i: u16 = 0; i < length; i++) {
338
- const txId = this.readBytes(32);
339
- const outputIndex = this.readU16();
340
- const scriptSig = this.readBytesWithLength();
341
- result[i] = new TransactionInput(txId, outputIndex, scriptSig);
342
- }
343
-
344
- return result;
345
- }
346
-
347
- public readTransactionOutputs(): TransactionOutput[] {
348
- const length = this.readU16();
349
- const result = new Array<TransactionOutput>(length);
350
-
351
- for (let i: u16 = 0; i < length; i++) {
352
- const index = this.readU16();
353
- const scriptPubKey = this.readStringWithLength();
354
- const value = this.readU64();
355
- result[i] = new TransactionOutput(index, scriptPubKey, value);
356
- }
357
-
358
- return result;
359
- }
360
-
361
332
  public getOffset(): i32 {
362
333
  return this.currentOffset;
363
334
  }
@@ -373,7 +344,7 @@ export class BytesReader {
373
344
  if (size > this.buffer.byteLength) {
374
345
  throw new Error(
375
346
  `Attempt to read beyond buffer length. Requested up to offset ${size}, ` +
376
- `but buffer is only ${this.buffer.byteLength} bytes.`,
347
+ `but buffer is only ${this.buffer.byteLength} bytes.`,
377
348
  );
378
349
  }
379
350
  }
@@ -0,0 +1,51 @@
1
+ // @ts-ignore
2
+ @external('env', '__atomic_wait32')
3
+ declare function __atomic_wait32(addr: i32, expected: i32, timeout: i64, proof: ArrayBuffer, verifier: ArrayBuffer): i32;
4
+
5
+ // @ts-ignore
6
+ @external('env', '__atomic_wait64')
7
+ declare function __atomic_wait64(addr: i32, expected: i64, timeout: i64, proof: ArrayBuffer, verifier: ArrayBuffer): i32;
8
+
9
+ // @ts-ignore
10
+ @external('env', '__atomic_notify')
11
+ declare function __atomic_notify(addr: i32, count: i32): i32;
12
+
13
+ // @ts-ignore
14
+ @external('env', '__thread_spawn')
15
+ declare function __thread_spawn(): i32;
16
+
17
+ export namespace Atomics {
18
+ export const OK: i32 = 0;
19
+ export const TIMED_OUT: i32 = 1;
20
+ export const NOT_EQUAL: i32 = 2;
21
+ export const NOT_AUTHORIZED: i32 = 3;
22
+ export const FAULT: i32 = -1;
23
+
24
+ /** Waits on `addr` until its value != `expected` or timeout (ns). */
25
+ export function wait32(addr: usize, expected: i32, timeoutNs: i64, proof: Uint8Array, verifier: Uint8Array): i32 {
26
+ WARNING('EXPERIMENTAL: wait32 is not yet stable and may change in the future. This feature is not available in production.');
27
+
28
+ return __atomic_wait32(<i32>addr, expected, timeoutNs, proof.buffer, verifier.buffer);
29
+ }
30
+
31
+ /** Waits on `addr` until its value != `expected` or timeout (ns). */
32
+ export function wait64(addr: usize, expected: i64, timeoutNs: i64, proof: Uint8Array, verifier: Uint8Array): i32 {
33
+ WARNING('EXPERIMENTAL: wait64 is not yet stable and may change in the future. This feature is not available in production.');
34
+
35
+ return __atomic_wait64(<i32>addr, expected, timeoutNs, proof.buffer, verifier.buffer);
36
+ }
37
+
38
+ /** Wakes up at most `count` waiters. */
39
+ export function notify(addr: usize, count: i32 = 1): i32 {
40
+ WARNING('EXPERIMENTAL: notify is not yet stable and may change in the future. This feature is not available in production.');
41
+
42
+ return __atomic_notify(<i32>addr, count);
43
+ }
44
+
45
+ /** Spawns a helper thread. */
46
+ export function spawn(): i32 {
47
+ WARNING('EXPERIMENTAL: spawn is not yet stable and may change in the future. This feature is not available in production.');
48
+
49
+ return __thread_spawn();
50
+ }
51
+ }
@@ -35,8 +35,6 @@ export * from '../env/global';
35
35
 
36
36
  @final
37
37
  export class BlockchainEnvironment {
38
- private static readonly MAX_U16: u16 = 65535;
39
-
40
38
  public readonly DEAD_ADDRESS: Address = Address.dead();
41
39
 
42
40
  private storage: MapUint8Array = new MapUint8Array();
@@ -81,7 +79,7 @@ export class BlockchainEnvironment {
81
79
  private _nextPointer: u16 = 0;
82
80
 
83
81
  public get nextPointer(): u16 {
84
- if (this._nextPointer === BlockchainEnvironment.MAX_U16) {
82
+ if (this._nextPointer === u16.MAX_VALUE) {
85
83
  throw new Revert(`Out of storage pointer.`);
86
84
  }
87
85
 
@@ -157,12 +155,7 @@ export class BlockchainEnvironment {
157
155
  const caller = reader.readAddress();
158
156
  const origin = reader.readAddress();
159
157
 
160
- this._tx = new Transaction(
161
- caller,
162
- origin,
163
- txId,
164
- txHash,
165
- );
158
+ this._tx = new Transaction(caller, origin, txId, txHash);
166
159
 
167
160
  this._contractDeployer = contractDeployer;
168
161
  this._contractAddress = contractAddress;
@@ -178,7 +171,12 @@ export class BlockchainEnvironment {
178
171
  }
179
172
 
180
173
  const resultLengthBuffer = new ArrayBuffer(32);
181
- const status = callContract(destinationContract.buffer, calldata.getBuffer().buffer, calldata.bufferLength(), resultLengthBuffer);
174
+ const status = callContract(
175
+ destinationContract.buffer,
176
+ calldata.getBuffer().buffer,
177
+ calldata.bufferLength(),
178
+ resultLengthBuffer,
179
+ );
182
180
 
183
181
  const reader = new BytesReader(Uint8Array.wrap(resultLengthBuffer));
184
182
  const resultLength = reader.readU32(true);
@@ -244,9 +242,7 @@ export class BlockchainEnvironment {
244
242
  return contractAddressReader.readAddress();
245
243
  }
246
244
 
247
- public getStorageAt(
248
- pointerHash: Uint8Array,
249
- ): Uint8Array {
245
+ public getStorageAt(pointerHash: Uint8Array): Uint8Array {
250
246
  this.hasPointerStorageHash(pointerHash);
251
247
  if (this.storage.has(pointerHash)) {
252
248
  return this.storage.get(pointerHash);
@@ -255,9 +251,7 @@ export class BlockchainEnvironment {
255
251
  return new Uint8Array(32);
256
252
  }
257
253
 
258
- public getTransientStorageAt(
259
- pointerHash: Uint8Array,
260
- ): Uint8Array {
254
+ public getTransientStorageAt(pointerHash: Uint8Array): Uint8Array {
261
255
  if (this.hasPointerTransientStorageHash(pointerHash)) {
262
256
  return this.transientStorage.get(pointerHash);
263
257
  }
@@ -3,16 +3,18 @@ import { TransactionInput, TransactionOutput } from './UTXO';
3
3
  import { Potential } from '../../lang/Definitions';
4
4
  import { BytesReader } from '../../buffer/BytesReader';
5
5
  import { getInputsSize, getOutputsSize, inputs, outputs } from '../global';
6
+ import { TransactionDecoder } from '../decoders/TransactionDecoder';
6
7
 
7
8
  @final
8
9
  export class Transaction {
10
+ private readonly transactionDecoder: TransactionDecoder = new TransactionDecoder();
11
+
9
12
  public constructor(
10
13
  public readonly sender: Address, // "immediate caller"
11
14
  public readonly origin: Address, // "leftmost thing in the call chain"
12
15
  public readonly txId: Uint8Array,
13
16
  public readonly hash: Uint8Array,
14
- ) {
15
- }
17
+ ) {}
16
18
 
17
19
  private _inputs: Potential<TransactionInput[]> = null;
18
20
 
@@ -46,7 +48,7 @@ export class Transaction {
46
48
  inputs(resultBuffer);
47
49
 
48
50
  const reader = new BytesReader(Uint8Array.wrap(resultBuffer));
49
- return reader.readTransactionInputs();
51
+ return this.transactionDecoder.readTransactionInputs(reader);
50
52
  }
51
53
 
52
54
  private loadOutputs(): TransactionOutput[] {
@@ -55,6 +57,6 @@ export class Transaction {
55
57
  outputs(resultBuffer);
56
58
 
57
59
  const reader = new BytesReader(Uint8Array.wrap(resultBuffer));
58
- return reader.readTransactionOutputs();
60
+ return this.transactionDecoder.readTransactionOutputs(reader);
59
61
  }
60
62
  }
@@ -1,10 +1,17 @@
1
+ import { TransactionInputFlags, TransactionOutputFlags } from '../enums/TransactionFlags';
2
+
1
3
  @final
2
4
  export class TransactionInput {
3
5
  public constructor(
6
+ public readonly flags: u8,
4
7
  public readonly txId: Uint8Array,
5
8
  public readonly outputIndex: u16,
6
9
  public readonly scriptSig: Uint8Array,
7
- ) {
10
+ public readonly coinbase: Uint8Array | null,
11
+ ) {}
12
+
13
+ public get isCoinbase(): boolean {
14
+ return (this.flags & TransactionInputFlags.hasCoinbase) !== 0;
8
15
  }
9
16
  }
10
17
 
@@ -12,8 +19,21 @@ export class TransactionInput {
12
19
  export class TransactionOutput {
13
20
  public constructor(
14
21
  public readonly index: u16,
15
- public readonly to: string,
22
+ public readonly flags: u8,
23
+ public readonly scriptPublicKey: Uint8Array | null,
24
+ public readonly to: string | null,
16
25
  public readonly value: u64,
17
- ) {
26
+ ) {}
27
+
28
+ public get hasTo(): boolean {
29
+ return (this.flags & TransactionOutputFlags.hasTo) !== 0;
30
+ }
31
+
32
+ public get hasScriptPubKey(): boolean {
33
+ return (this.flags & TransactionOutputFlags.hasScriptPubKey) !== 0;
34
+ }
35
+
36
+ public get isOPReturn(): boolean {
37
+ return (this.flags & TransactionOutputFlags.OP_RETURN) !== 0;
18
38
  }
19
39
  }
@@ -0,0 +1,66 @@
1
+ import { TransactionInput, TransactionOutput } from '../classes/UTXO';
2
+ import { TransactionInputFlags, TransactionOutputFlags } from '../enums/TransactionFlags';
3
+ import { BytesReader } from '../../buffer/BytesReader';
4
+
5
+ export class TransactionDecoder {
6
+ public readTransactionInputs(buffer: BytesReader): TransactionInput[] {
7
+ const length = buffer.readU16();
8
+ const result = new Array<TransactionInput>(length);
9
+
10
+ for (let i: u16 = 0; i < length; i++) {
11
+ result[i] = this.decodeInput(buffer);
12
+ }
13
+
14
+ return result;
15
+ }
16
+
17
+ public readTransactionOutputs(buffer: BytesReader): TransactionOutput[] {
18
+ const length = buffer.readU16();
19
+ const result = new Array<TransactionOutput>(length);
20
+
21
+ for (let i: u16 = 0; i < length; i++) {
22
+ result[i] = this.decodeOutput(buffer);
23
+ }
24
+
25
+ return result;
26
+ }
27
+
28
+ private decodeInput(buffer: BytesReader): TransactionInput {
29
+ const flags = buffer.readU8();
30
+ const txId = buffer.readBytes(32);
31
+ const outputIndex = buffer.readU16();
32
+ const scriptSig = buffer.readBytesWithLength();
33
+
34
+ const coinbase: Uint8Array | null = this.hasFlag(flags, TransactionInputFlags.hasCoinbase)
35
+ ? buffer.readBytesWithLength()
36
+ : null;
37
+
38
+ return new TransactionInput(flags, txId, outputIndex, scriptSig, coinbase);
39
+ }
40
+
41
+ private decodeOutput(buffer: BytesReader): TransactionOutput {
42
+ const flags = buffer.readU8();
43
+ const index = buffer.readU16();
44
+
45
+ let scriptPubKey: Uint8Array | null = null;
46
+ if (this.hasFlag(flags, TransactionOutputFlags.hasScriptPubKey)) {
47
+ scriptPubKey = buffer.readBytesWithLength();
48
+ }
49
+
50
+ let to: string | null = null;
51
+ if (this.hasFlag(flags, TransactionOutputFlags.hasTo)) {
52
+ to = buffer.readStringWithLength();
53
+ }
54
+
55
+ const value = buffer.readU64();
56
+
57
+ return new TransactionOutput(index, flags, scriptPubKey, to, value);
58
+ }
59
+
60
+ /**
61
+ * Checks if the given flag is set in the flags byte.
62
+ */
63
+ private hasFlag<T extends u8>(flags: u8, flag: T): boolean {
64
+ return (flags & flag) !== 0;
65
+ }
66
+ }
@@ -0,0 +1,9 @@
1
+ export enum TransactionInputFlags {
2
+ hasCoinbase = 0b00000001,
3
+ }
4
+
5
+ export enum TransactionOutputFlags {
6
+ hasTo = 0b00000001,
7
+ hasScriptPubKey = 0b00000010,
8
+ OP_RETURN = 0b00000100,
9
+ }
@@ -98,3 +98,5 @@ export declare function getAccountType(address: ArrayBuffer): u32;
98
98
  // @ts-ignore
99
99
  @external('env', 'exit')
100
100
  export declare function env_exit(status: u32, data: ArrayBuffer, dataLength: u32): void;
101
+
102
+ export * from './Atomic';
package/runtime/index.ts CHANGED
@@ -61,10 +61,15 @@ export * from './nested/codecs/VariableBytesCodec';
61
61
  /** Storage */
62
62
  export * from './storage/StoredU256';
63
63
  export * from './storage/StoredU64';
64
+ export * from './storage/StoredU32';
64
65
  export * from './storage/StoredString';
66
+ export * from './storage/AdvancedStoredString';
65
67
  export * from './storage/StoredAddress';
66
68
  export * from './storage/StoredBoolean';
67
69
 
70
+ /** Maps */
71
+ export * from './storage/maps/StoredMapU256';
72
+
68
73
  /** Arrays */
69
74
  export * from './storage/arrays/StoredAddressArray';
70
75
  export * from './storage/arrays/StoredBooleanArray';
@@ -133,14 +133,14 @@ export function setBit(buffer: Uint8Array, bitIndex: u16, bitValue: bool): void
133
133
  * Assume the data is at least 16 bytes, read two u64s from it in big-endian order.
134
134
  */
135
135
  @inline
136
- export function readLengthAndStartIndex(data: Uint8Array): u64[] {
136
+ export function readLengthAndStartIndex(data: Uint8Array): u32[] {
137
137
  if (data.length < 16) {
138
138
  return [0, 0];
139
139
  }
140
140
 
141
141
  const reader = new BytesReader(data);
142
- const length = reader.readU64();
143
- const startIndex = reader.readU64();
142
+ const length = reader.readU32();
143
+ const startIndex = reader.readU32();
144
144
 
145
145
  return [length, startIndex];
146
146
  }
@@ -149,10 +149,10 @@ export function readLengthAndStartIndex(data: Uint8Array): u64[] {
149
149
  * Write two u64s into a 32-byte buffer in big-endian order
150
150
  */
151
151
  @inline
152
- export function writeLengthAndStartIndex(length: u64, startIndex: u64): Uint8Array {
152
+ export function writeLengthAndStartIndex(length: u32, startIndex: u32): Uint8Array {
153
153
  const writer = new BytesWriter(32);
154
- writer.writeU64(length);
155
- writer.writeU64(startIndex);
154
+ writer.writeU32(length);
155
+ writer.writeU32(startIndex);
156
156
 
157
157
  return writer.getBuffer();
158
158
  }
@@ -174,4 +174,4 @@ export function bigEndianAdd(base: Uint8Array, increment: u64): Uint8Array {
174
174
  const add = u64ToBE32Bytes(increment);
175
175
 
176
176
  return addUint8ArraysBE(base, add);
177
- }
177
+ }
@@ -0,0 +1,199 @@
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;
7
+
8
+ /**
9
+ * @class AdvancedStoredString
10
+ * @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).
16
+ */
17
+ @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);
48
+ return bigEndianAdd(base, chunkIndex);
49
+ }
50
+
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);
198
+ }
199
+ }