Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Offset is outside the bounds of the DataView issue for TransactionV1 #479

Merged
merged 1 commit into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions src/types/Args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;

Expand Down
38 changes: 35 additions & 3 deletions src/types/ByteConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
44 changes: 33 additions & 11 deletions src/types/TransactionV1Payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
}
Expand Down
Loading