Skip to content

Commit

Permalink
Merge pull request #479 from casper-ecosystem/fix-offset-for-arraybuffer
Browse files Browse the repository at this point in the history
Fix Offset is outside the bounds of the DataView issue for TransactionV1
  • Loading branch information
alexmyshchyshyn authored Dec 23, 2024
2 parents 766f6da + 93562ca commit a4acbda
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 20 deletions.
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

0 comments on commit a4acbda

Please sign in to comment.