Skip to content

Commit

Permalink
Fixed serialization and deserialization for transaction hash with Tra…
Browse files Browse the repository at this point in the history
…nsactionV1Payload, removed TransactionCategory from Transaction
  • Loading branch information
alexmyshchyshyn committed Nov 27, 2024
1 parent 01b8942 commit 961d829
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 138 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ const paymentAmount = '20000000000000';

const pricingMode = new PricingMode();
const fixedMode = new FixedMode();
fixedMode.gasPriceTolerance = 3;
fixedMode.additionalComputationFactor = 1;
fixedMode.gasPriceTolerance = 1;
fixedMode.additionalComputationFactor = 0;
pricingMode.fixed = fixedMode;

const args = Args.fromMap({
Expand All @@ -155,7 +155,7 @@ const args = Args.fromMap({
)
),
amount: CLValueUInt512.newCLUInt512(paymentAmount),
id: CLValueOption.newCLOption(CLValueUInt64.newCLUint64(3))
id: CLValueOption.newCLOption(CLValueUInt64.newCLUint64(3)) // memo ( optional )
});

const transactionTarget = new TransactionTarget({}); // Native target;
Expand Down
41 changes: 39 additions & 2 deletions src/types/Args.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { concat } from '@ethersproject/bytes';
import { jsonMapMember, jsonObject } from 'typedjson';

import { CLValue, CLValueParser } from './clvalue';
import { jsonMapMember, jsonObject } from 'typedjson';
import { toBytesString, toBytesU32 } from './ByteConverters';
import { toBytesString, toBytesU32, writeInteger } from './ByteConverters';

/**
* Represents a named argument with a name and associated `CLValue`, which can be serialized to bytes.
Expand All @@ -25,6 +25,43 @@ export class NamedArg {
return concat([name, value]);
}

/**
* Converts a `NamedArg` object to a `Uint8Array` for serialization.
*
* The method encodes the name of the argument as a UTF-8 string, followed by the serialized
* bytes of its value. The resulting `Uint8Array` can be used for further processing, such as
* storage or transmission.
*
* @param source - The `NamedArg` object to serialize. It contains a name and a value.
* @returns A `Uint8Array` representing the serialized `NamedArg`.
*
* @example
* ```typescript
* const namedArg = new NamedArg("arg1", CLValue.u32(42));
* const serializedBytes = YourClass.toBytesWithNamedArg(namedArg);
* console.log(serializedBytes); // Logs the serialized bytes.
* ```
*/
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);
let offset = 0;

const nameBytes = new TextEncoder().encode(source.name);
offset = writeInteger(view, offset, nameBytes.length);
new Uint8Array(buffer, offset).set(nameBytes);
offset += nameBytes.length;

const valueBytes = CLValueParser.toBytesWithType(source.value);
new Uint8Array(buffer, offset).set(valueBytes);
offset += valueBytes.length;

return new Uint8Array(buffer, 0, offset);
}

/**
* Creates a `NamedArg` instance from a byte array.
* @param bytes - The byte array to parse.
Expand Down
86 changes: 86 additions & 0 deletions src/types/ByteConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,89 @@ export const fromBytesU64 = (bytes: Uint8Array): BigNumber => {
// Convert the little-endian bytes into a BigNumber
return BigNumber.from(bytes.reverse());
};

/**
* Writes a 32-bit signed integer to a `DataView` at the specified offset.
*
* The integer is written in little-endian format.
*
* @param view - The `DataView` instance where the integer will be written.
* @param offset - The offset (in bytes) at which to start writing.
* @param value - The 32-bit signed integer to write.
* @returns The new offset after writing the integer.
*
* @example
* ```typescript
* const buffer = new ArrayBuffer(8);
* const view = new DataView(buffer);
* let offset = 0;
* offset = writeInteger(view, offset, 42);
* console.log(new Int32Array(buffer)); // Logs: Int32Array [42, 0]
* ```
*/
export const writeInteger = (
view: DataView,
offset: number,
value: number
): number => {
view.setInt32(offset, value, true);
return offset + 4;
};

/**
* Writes a 16-bit unsigned integer to a `DataView` at the specified offset.
*
* The integer is written in little-endian format.
*
* @param view - The `DataView` instance where the integer will be written.
* @param offset - The offset (in bytes) at which to start writing.
* @param value - The 16-bit unsigned integer to write.
* @returns The new offset after writing the integer.
*
* @example
* ```typescript
* const buffer = new ArrayBuffer(4);
* const view = new DataView(buffer);
* let offset = 0;
* offset = writeUShort(view, offset, 65535);
* console.log(new Uint16Array(buffer)); // Logs: Uint16Array [65535, 0]
* ```
*/
export const writeUShort = (
view: DataView,
offset: number,
value: number
): number => {
view.setUint16(offset, value, true);
return offset + 2;
};

/**
* Writes a sequence of bytes (as a `Uint8Array`) to a `DataView` at the specified offset.
*
* Each byte in the array is written in sequence, starting from the given offset.
*
* @param view - The `DataView` instance where the bytes will be written.
* @param offset - The offset (in bytes) at which to start writing.
* @param value - The `Uint8Array` containing the bytes to write.
* @returns The new offset after writing the bytes.
*
* @example
* ```typescript
* const buffer = new ArrayBuffer(10);
* const view = new DataView(buffer);
* let offset = 0;
* offset = writeBytes(view, offset, new Uint8Array([1, 2, 3, 4]));
* console.log(new Uint8Array(buffer)); // Logs: Uint8Array [1, 2, 3, 4, 0, 0, 0, 0, 0, 0]
* ```
*/
export const writeBytes = (
view: DataView,
offset: number,
value: Uint8Array
): number => {
value.forEach((byte, index) => {
view.setUint8(offset + index, byte);
});
return offset + value.length;
};
5 changes: 1 addition & 4 deletions src/types/Deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Hash } from './key';
import { HexBytes } from './HexBytes';
import { PublicKey, PrivateKey } from './keypair';
import { Duration, Timestamp } from './Time';
import { Approval, Transaction, TransactionCategory } from './Transaction';
import { Approval, Transaction } from './Transaction';
import {
TransactionEntryPoint,
TransactionEntryPointEnum
Expand Down Expand Up @@ -357,10 +357,8 @@ export class Deploy {
static newTransactionFromDeploy(deploy: Deploy): Transaction {
let paymentAmount = 0;
let transactionEntryPoint: TransactionEntryPoint;
let transactionCategory = TransactionCategory.Large;

if (deploy.session.transfer) {
transactionCategory = TransactionCategory.Mint;
transactionEntryPoint = new TransactionEntryPoint(
TransactionEntryPointEnum.Transfer
);
Expand Down Expand Up @@ -414,7 +412,6 @@ export class Deploy {
transactionEntryPoint,
new TransactionScheduling({ standard: {} }),
deploy.approvals,
transactionCategory,
undefined,
deploy
);
Expand Down
24 changes: 16 additions & 8 deletions src/types/ExecutableDeployItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CLTypeOption,
CLTypeUInt64,
CLValue,
CLValueByteArray,
CLValueOption,
CLValueString,
CLValueUInt32,
Expand All @@ -21,7 +22,6 @@ import {
serializeArgs
} from './SerializationUtils';
import { PublicKey } from './keypair';
import { toBytesArrayU8 } from './ByteConverters';

/**
* Enum representing the different types of executable deploy items.
Expand Down Expand Up @@ -49,7 +49,7 @@ export class ModuleBytes {
serializer: byteArrayJsonSerializer,
deserializer: byteArrayJsonDeserializer
})
moduleBytes: Uint8Array;
moduleBytes!: Uint8Array;

/**
* The arguments passed to the module.
Expand All @@ -75,13 +75,21 @@ export class ModuleBytes {
* @returns The serialized byte array.
*/
bytes(): Uint8Array {
if (!this.args) throw new Error('Missing arguments for ModuleBytes');
const lengthBytes = CLValueUInt32.newCLUInt32(
BigNumber.from(this.moduleBytes.length)
).bytes();
const bytesArrayBytes = CLValueByteArray.newCLByteArray(
this.moduleBytes
).bytes();

let result = concat([lengthBytes, bytesArrayBytes]);

if (this.args) {
const argBytes = this.args.toBytes();
result = concat([result, argBytes]);
}

return concat([
Uint8Array.from([0]),
toBytesArrayU8(this.moduleBytes),
this.args.toBytes()
]);
return result;
}
}

Expand Down
56 changes: 33 additions & 23 deletions src/types/SerializationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,39 +126,49 @@ export const dehumanizerTTL = (ttl: string): number => {
/**
* Deserializes an array of runtime arguments to a `RuntimeArgs` object.
*
* @param arr The array of serialized runtime arguments.
* @param arr The array of serialized runtime arguments or a Named wrapper.
* @returns A `RuntimeArgs` object containing the deserialized arguments.
* @throws Error if the input format is invalid.
*/
export const deserializeArgs = (arr: any) => {
export const deserializeArgs = (arr: any): Args | undefined => {
const raSerializer = new TypedJSON(Args);
const value = {
args: arr
};
return raSerializer.parse(value);

if (arr.Named && Array.isArray(arr.Named)) {
// If the arguments are wrapped in a "Named" property
return raSerializer.parse({ args: arr.Named });
}

if (Array.isArray(arr)) {
// If the input is directly an array of arguments
return raSerializer.parse({ args: arr });
}

throw new Error('Invalid argument format for deserialization.');
};

/**
* Serializes a `RuntimeArgs` object to a byte array.
* Serializes a `RuntimeArgs` object to a byte array or an object representation.
*
* This function converts the `RuntimeArgs` (or `Args`) object into a serialized format.
* If `asNamed` is set to `true`, the serialized arguments are wrapped in a `Named` property
* for more structured output. Otherwise, the plain array of serialized arguments is returned.
*
* @param ra - The `Args` object to be serialized. It contains the runtime arguments.
* @param asNamed - A boolean flag indicating whether to wrap the serialized output in a `Named` property. Defaults to `false`.
* @returns A serialized representation of the runtime arguments.
* If `asNamed` is `true`, the output is an object with a `Named` property. Otherwise, it is a plain array.
*
* @param ra The `RuntimeArgs` object to be serialized.
* @returns A byte array representing the serialized runtime arguments.
*/
export const serializeArgs = (ra: Args) => {
export const serializeArgs = (ra: Args, asNamed = false) => {
const raSerializer = new TypedJSON(Args);
const json = raSerializer.toPlainJson(ra);
return Object.values(json as any)[0];
};
const argsArray = Object.values(json as any)[0];

/**
* Compares two arrays for equality.
* @param a The first array.
* @param b The second array.
* @returns `true` if the arrays are equal, `false` otherwise.
*/
export const arrayEquals = (a: Uint8Array, b: Uint8Array): boolean => {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
if (asNamed) {
return {
Named: argsArray
};
}
return true;

return argsArray;
};
10 changes: 0 additions & 10 deletions src/types/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,13 +373,6 @@ export class Transaction {
})
public scheduling: TransactionScheduling;

/**
* The category of the transaction, indicating its type (e.g., minting, auction).
* Using TransactionCategory as enum
*/
@jsonMember({ name: 'transaction_category', constructor: Number })
public category?: number;

/**
* The list of approvals for this transaction.
*/
Expand Down Expand Up @@ -427,7 +420,6 @@ export class Transaction {
entryPoint: TransactionEntryPoint,
scheduling: TransactionScheduling,
approvals: Approval[],
category?: TransactionCategory,
originTransactionV1?: TransactionV1,
originDeployV1?: Deploy
) {
Expand All @@ -442,7 +434,6 @@ export class Transaction {
this.entryPoint = entryPoint;
this.scheduling = scheduling;
this.approvals = approvals;
this.category = category;

this.originDeployV1 = originDeployV1;
this.originTransactionV1 = originTransactionV1;
Expand Down Expand Up @@ -482,7 +473,6 @@ export class Transaction {
v1.payload.fields.entryPoint,
v1.payload.fields.scheduling,
v1.approvals,
undefined,
v1, // originTransactionV1
undefined // originDeployV1 is not applicable for this method
);
Expand Down
2 changes: 1 addition & 1 deletion src/types/TransactionEntryPoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export class TransactionEntryPoint {
*
* @returns A `Uint8Array` representing the transaction entry point and any associated data.
*/
bytes(): Uint8Array {
toBytes(): Uint8Array {
const calltableSerialization = new CalltableSerialization();
const tag = this.tag();
calltableSerialization.addField(0, Uint8Array.from([tag]));
Expand Down
2 changes: 1 addition & 1 deletion src/types/TransactionScheduling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export class TransactionScheduling {
*
* @returns A `Uint8Array` representing the transaction scheduling.
*/
bytes(): Uint8Array {
toBytes(): Uint8Array {
if (this.standard) {
const calltableSerialization = new CalltableSerialization();
calltableSerialization.addField(0, Uint8Array.of(0));
Expand Down
Loading

0 comments on commit 961d829

Please sign in to comment.