diff --git a/src/types/Args.ts b/src/types/Args.ts index bccfc74a..490a4f79 100644 --- a/src/types/Args.ts +++ b/src/types/Args.ts @@ -2,7 +2,12 @@ import { concat } from '@ethersproject/bytes'; import { jsonMapMember, jsonObject } from 'typedjson'; import { CLValue, CLValueParser } from './clvalue'; -import { toBytesString, toBytesU32, writeInteger } from './ByteConverters'; +import { + expandBuffer, + toBytesString, + toBytesU32, + writeInteger +} from './ByteConverters'; /** * Represents a named argument with a name and associated `CLValue`, which can be serialized to bytes. @@ -43,19 +48,25 @@ export class NamedArg { * ``` */ public static toBytesWithNamedArg(source: NamedArg): Uint8Array { - // The buffer size is fixed at 1024 bytes based on the expected maximum size of - // encoded data, with room for edge cases. If inputs exceed this size, revisit - // the implementation. - const buffer = new ArrayBuffer(1024); - const view = new DataView(buffer); + const bufferSize = 1024; + let buffer = new ArrayBuffer(bufferSize); + let view = new DataView(buffer); let offset = 0; const nameBytes = new TextEncoder().encode(source.name); + if (offset + 4 + nameBytes.length > buffer.byteLength) { + buffer = expandBuffer(buffer, offset + 4 + nameBytes.length); + view = new DataView(buffer); + } offset = writeInteger(view, offset, nameBytes.length); new Uint8Array(buffer, offset).set(nameBytes); offset += nameBytes.length; const valueBytes = CLValueParser.toBytesWithType(source.value); + if (offset + valueBytes.length > buffer.byteLength) { + buffer = expandBuffer(buffer, offset + valueBytes.length); + view = new DataView(buffer); + } new Uint8Array(buffer, offset).set(valueBytes); offset += valueBytes.length; diff --git a/src/types/ByteConverters.ts b/src/types/ByteConverters.ts index 33babb03..9d3790d3 100644 --- a/src/types/ByteConverters.ts +++ b/src/types/ByteConverters.ts @@ -254,8 +254,40 @@ export const writeBytes = ( offset: number, value: Uint8Array ): number => { - value.forEach((byte, index) => { - view.setUint8(offset + index, byte); - }); + for (let i = 0; i < value.length; i++) { + view.setUint8(offset + i, value[i]); + } return offset + value.length; }; + +/** + * Expands the size of an existing ArrayBuffer to accommodate additional data if necessary. + * + * This function creates a new `ArrayBuffer` with a size that is at least as large as + * the required size. The buffer's size grows exponentially (doubles) to minimize + * reallocations and improve performance for large data handling. + * The existing data from the old buffer is copied into the new buffer. + * + * @param currentBuffer - The current `ArrayBuffer` that needs to be expanded. + * @param requiredSize - The minimum size required for the buffer. + * @returns A new `ArrayBuffer` with enough space to accommodate the required size. + * + * @example + * ```typescript + * let buffer = new ArrayBuffer(1024); + * const updatedBuffer = expandBuffer(buffer, 2048); + * console.log(updatedBuffer.byteLength); // 2048 or larger (depending on initial size and required size) + * ``` + */ +export const expandBuffer = ( + currentBuffer: ArrayBuffer, + requiredSize: number +): ArrayBuffer => { + let newSize = currentBuffer.byteLength; + while (newSize < requiredSize) { + newSize *= 2; // Double the buffer size until it fits + } + const newBuffer = new ArrayBuffer(newSize); + new Uint8Array(newBuffer).set(new Uint8Array(currentBuffer)); // Copy existing data + return newBuffer; +}; diff --git a/src/types/TransactionV1Payload.ts b/src/types/TransactionV1Payload.ts index 09bd000c..cc8b493c 100644 --- a/src/types/TransactionV1Payload.ts +++ b/src/types/TransactionV1Payload.ts @@ -10,7 +10,12 @@ import { TransactionScheduling } from './TransactionScheduling'; import { CalltableSerialization } from './CalltableSerialization'; import { deserializeArgs, serializeArgs } from './SerializationUtils'; import { CLValueString, CLValueUInt64 } from './clvalue'; -import { writeBytes, writeInteger, writeUShort } from './ByteConverters'; +import { + expandBuffer, + writeBytes, + writeInteger, + writeUShort +} from './ByteConverters'; /** * Interface representing the parameters required to build a `TransactionV1Payload`. @@ -186,17 +191,24 @@ export class PayloadFields { * */ toBytes(): Uint8Array { - // The buffer size is fixed at 1024 bytes based on the expected maximum size of - // encoded data, with room for edge cases. If inputs exceed this size, revisit - // the implementation. - const fieldsBytes = new ArrayBuffer(1024); - const view = new DataView(fieldsBytes); + const bufferSize = 1024; + let fieldsBytes = new ArrayBuffer(bufferSize); + let view = new DataView(fieldsBytes); let offset = 0; offset = writeInteger(view, offset, this.fields.size); for (const [field, value] of Array.from(this.fields.entries())) { + if (offset + 2 > fieldsBytes.byteLength) { + fieldsBytes = expandBuffer(fieldsBytes, offset + 2); + view = new DataView(fieldsBytes); + } offset = writeUShort(view, offset, field); + + if (offset + value.length > fieldsBytes.byteLength) { + fieldsBytes = expandBuffer(fieldsBytes, offset + value.length); + view = new DataView(fieldsBytes); + } offset = writeBytes(view, offset, value); } @@ -269,22 +281,32 @@ export class TransactionV1Payload { * @returns A `Uint8Array` representing the serialized transaction payload. */ toBytes(): Uint8Array { - // The buffer size is fixed at 1024 bytes based on the expected maximum size of - // encoded data, with room for edge cases. If inputs exceed this size, revisit - // the implementation. - const runtimeArgsBuffer = new ArrayBuffer(1024); - const runtimeArgsView = new DataView(runtimeArgsBuffer); + const bufferSize = 1024; + let runtimeArgsBuffer = new ArrayBuffer(bufferSize); + let runtimeArgsView = new DataView(runtimeArgsBuffer); let offset = 0; runtimeArgsView.setUint8(offset, 0x00); offset += 1; + if (offset + 4 > runtimeArgsBuffer.byteLength) { + runtimeArgsBuffer = expandBuffer(runtimeArgsBuffer, offset + 4); + runtimeArgsView = new DataView(runtimeArgsBuffer); + } runtimeArgsView.setUint32(offset, this.fields.args.args.size, true); offset += 4; for (const [name, value] of Array.from(this.fields.args.args.entries())) { const namedArg = new NamedArg(name, value); const argBytes = NamedArg.toBytesWithNamedArg(namedArg); + + if (offset + argBytes.length > runtimeArgsBuffer.byteLength) { + runtimeArgsBuffer = expandBuffer( + runtimeArgsBuffer, + offset + argBytes.length + ); + runtimeArgsView = new DataView(runtimeArgsBuffer); + } new Uint8Array(runtimeArgsBuffer, offset).set(argBytes); offset += argBytes.length; }