Skip to content

Commit

Permalink
Added Deserializer for TransactionV1Payload, TransactionEntryPoint, T…
Browse files Browse the repository at this point in the history
…ransactionScheduling, TransactionTarget, CalltableSerialization to convert fromBytes to instances, Updated Reservation for BidKind, Added Prepaid for StoredValue, added fromBytesWithType for CLValueParser
  • Loading branch information
alexmyshchyshyn committed Nov 23, 2024
1 parent 484bbdd commit efc7f80
Show file tree
Hide file tree
Showing 18 changed files with 958 additions and 360 deletions.
18 changes: 0 additions & 18 deletions src/types/AddressableEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,21 +152,3 @@ export class NamedEntryPoint {
@jsonMember({ name: 'name', constructor: String })
name: string;
}

/**
* Returns the numeric tag associated with a given transaction runtime version.
* Useful for distinguishing between different virtual machine versions.
*
* @param runtime - The transaction runtime to retrieve the tag for.
* @returns A number representing the tag for the given runtime.
*/
export function getRuntimeTag(runtime: TransactionRuntime): number {
switch (runtime) {
case 'VmCasperV1':
return 0;
case 'VmCasperV2':
return 1;
default:
return 0;
}
}
73 changes: 34 additions & 39 deletions src/types/Args.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import { concat } from '@ethersproject/bytes';

import {
CLTypeString,
CLValue,
CLValueParser,
CLValueString,
CLValueUInt32,
IResultWithBytes
} from './clvalue';
import { CLValue, CLValueParser } from './clvalue';
import { jsonMapMember, jsonObject } from 'typedjson';
import { toBytesString, toBytesU32 } from './ByteConverters';

Expand Down Expand Up @@ -35,21 +28,21 @@ export class NamedArg {
/**
* Creates a `NamedArg` instance from a byte array.
* @param bytes - The byte array to parse.
* @returns A new `NamedArg` instance.
* @throws Error if the value data is missing.
* @returns A `NamedArg` instance.
*/
public static fromBytes(bytes: Uint8Array): NamedArg {
const stringValue = CLValueString.fromBytes(bytes);
let offset = 0;

if (!stringValue.bytes) {
throw new Error('Missing data for value of named arg');
}
const nameLength = new DataView(bytes.buffer).getUint32(offset, true);
offset += 4;
const nameBytes = bytes.slice(offset, offset + nameLength);
offset += nameLength;
const name = new TextDecoder().decode(nameBytes);

const value = CLValueParser.fromBytesByType(
stringValue.bytes,
CLTypeString
);
return new NamedArg(value.result.toString(), value.result);
const valueBytes = bytes.slice(offset);
const value = CLValueParser.fromBytesWithType(valueBytes);

return new NamedArg(name, value.result);
}
}

Expand Down Expand Up @@ -156,28 +149,30 @@ export class Args {

/**
* Creates an `Args` instance from a byte array.
* Parses the byte array to extract each argument.
* @param bytes - The byte array to parse.
* @returns An object containing a new `Args` instance and any remaining bytes.
* @throws Error if there is an issue parsing the bytes.
* @returns An `Args` instance.
*/
public static fromBytes(bytes: Uint8Array): IResultWithBytes<Args> {
const uint32 = CLValueUInt32.fromBytes(bytes);
const size = uint32.result.getValue().toNumber();

let remainBytes: Uint8Array | undefined = uint32.bytes;
const res: NamedArg[] = [];
for (let i = 0; i < size; i++) {
if (!remainBytes) {
throw new Error('Error while parsing bytes');
}
const namedArg = NamedArg.fromBytes(remainBytes);
res.push(namedArg);
remainBytes = undefined;
public static fromBytes(bytes: Uint8Array): Args {
let offset = 0;

const numArgs = new DataView(bytes.buffer).getUint32(offset, true);
offset += 4;

const args = new Map<string, CLValue>();

for (let i = 0; i < numArgs; i++) {
const namedArgBytes = bytes.slice(offset);
const namedArg = NamedArg.fromBytes(namedArgBytes);

const nameLength = new DataView(namedArgBytes.buffer).getUint32(0, true);
const valueBytes = CLValueParser.toBytesWithType(namedArg.value);
const consumedBytes = 4 + nameLength + valueBytes.length;

offset += consumedBytes;

args.set(namedArg.name, namedArg.value);
}
return {
result: Args.fromNamedArgs(res),
bytes: remainBytes || Uint8Array.from([])
};

return new Args(args);
}
}
44 changes: 44 additions & 0 deletions src/types/Bid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ export class ValidatorBid {
@jsonMember({ name: 'maximum_delegation_amount', constructor: Number })
maximumDelegationAmount: number;

/**
* Number of slots reserved for specific delegators
*/
@jsonMember({ name: 'reserved_slots', constructor: Number })
reservedSlots: number;

/**
* The vesting schedule for this validator’s stake.
*/
Expand Down Expand Up @@ -301,3 +307,41 @@ export class Bridge {
})
newValidatorPublicKey: PublicKey;
}

@jsonObject
/**
* Represents a reservation in the blockchain system, including delegation details and associated public keys.
*/
export class Reservation {
/**
* The delegation rate, representing the percentage of rewards allocated to the delegator.
*/
@jsonMember({ name: 'delegation_rate', constructor: Number })
delegationRate: number;

/**
* The public key of the validator associated with this reservation.
*
* This key is used to identify the validator in the blockchain system.
*/
@jsonMember({
name: 'validator_public_key',
constructor: PublicKey,
deserializer: json => PublicKey.fromJSON(json),
serializer: value => value.toJSON()
})
validatorPublicKey: PublicKey;

/**
* The public key of the delegator associated with this reservation.
*
* This key is used to identify the delegator who initiated the reservation.
*/
@jsonMember({
name: 'delegator_public_key',
constructor: PublicKey,
deserializer: json => PublicKey.fromJSON(json),
serializer: value => value.toJSON()
})
delegatorPublicKey: PublicKey;
}
15 changes: 14 additions & 1 deletion src/types/BidKind.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { jsonObject, jsonMember } from 'typedjson';
import { Bid, Bridge, Credit, Delegator, ValidatorBid } from './Bid';
import {
Bid,
Bridge,
Credit,
Delegator,
Reservation,
ValidatorBid
} from './Bid';

/**
* Represents a polymorphic bid kind, allowing for different types of bid-related entities.
Expand Down Expand Up @@ -37,4 +44,10 @@ export class BidKind {
*/
@jsonMember({ name: 'Credit', constructor: Credit })
credit?: Credit;

/**
* Represents a validator reserving a slot for specific delegator
*/
@jsonMember({ name: 'Reservation', constructor: Reservation })
reservation?: Reservation;
}
41 changes: 41 additions & 0 deletions src/types/ByteConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,44 @@ export function toBytesArrayU8(arr: Uint8Array): Uint8Array {
export function byteHash(x: Uint8Array): Uint8Array {
return blake2b(x, { dkLen: 32 });
}

/**
* Parses a 16-bit unsigned integer (`u16`) from a little-endian byte array.
* @param bytes - The byte array containing the `u16` value.
* @returns The parsed 16-bit unsigned integer.
*/
export function parseU16(bytes: Uint8Array): number {
if (bytes.length < 2) {
throw new Error('Invalid byte array for u16 parsing');
}
return bytes[0] | (bytes[1] << 8);
}

/**
* Parses a 32-bit unsigned integer (`u32`) from a little-endian byte array.
* @param bytes - The byte array containing the `u32` value.
* @returns The parsed 32-bit unsigned integer.
*/
export function parseU32(bytes: Uint8Array): number {
if (bytes.length < 4) {
throw new Error('Invalid byte array for u32 parsing');
}

return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
}

/**
* Parses a 64-bit unsigned integer (`u64`) from a little-endian byte array.
* @param bytes - A `Uint8Array` containing the serialized 64-bit unsigned integer.
* @returns A `BigNumber` representing the parsed value.
*/
export const fromBytesU64 = (bytes: Uint8Array): BigNumber => {
if (bytes.length !== 8) {
throw new Error(
`Invalid input length for u64: expected 8 bytes, got ${bytes.length}`
);
}

// Convert the little-endian bytes into a BigNumber
return BigNumber.from(bytes.reverse());
};
97 changes: 88 additions & 9 deletions src/types/CalltableSerialization.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { concat } from '@ethersproject/bytes';
import { toBytesU16, toBytesU32 } from './ByteConverters';
import { parseU16, parseU32, toBytesU16, toBytesU32 } from './ByteConverters';

/**
* Represents a single field in the call table.
*/
export class Field {
readonly index: number;
readonly offset: number;
readonly value: Uint8Array;
value: Uint8Array;

/**
* Constructs a new `Field` instance.
*
* @param index - The index of the field.
* @param offset - The offset of the field in the payload.
* @param value - The byte array value of the field.
*/
constructor(index: number, offset: number, value: Uint8Array) {
this.index = index;
this.offset = offset;
Expand All @@ -14,22 +24,35 @@ export class Field {

/**
* Calculates the serialized vector size for the given number of fields.
* @returns The size of the serialized vector.
*
* This method determines the size of the serialized vector required
* to store all fields, including their indices and offsets.
*
* @returns The size of the serialized vector in bytes.
*/
static serializedVecSize(): number {
return 4 + 4 * 2;
return 4 + 4 * 2; // Number of fields (4 bytes) + index/offset pairs (4 bytes each)
}
}

/**
* Handles serialization and deserialization of call table data.
*
* The `CalltableSerialization` class is responsible for managing a collection
* of fields and converting them into a byte array for serialization. It can
* also reconstruct the fields from a serialized byte array.
*/
export class CalltableSerialization {
private fields: Field[] = [];
private currentOffset = 0;

/**
* Adds a field to the call table.
* @param index The field index.
* @param value The field value as a byte array.
* @returns The current instance of CalltableSerialization.
*
* @param index - The field index.
* @param value - The field value as a byte array.
* @returns The current instance of `CalltableSerialization`.
* @throws An error if the fields are not added in the correct index order.
*/
addField(index: number, value: Uint8Array): CalltableSerialization {
if (this.fields.length !== index) {
Expand All @@ -44,8 +67,9 @@ export class CalltableSerialization {
}

/**
* Serializes the call table to a byte array.
* @returns A Uint8Array representing the serialized call table.
* Serializes the call table into a byte array.
*
* @returns A `Uint8Array` representing the serialized call table.
*/
toBytes(): Uint8Array {
const calltableBytes: Uint8Array[] = [];
Expand All @@ -63,4 +87,59 @@ export class CalltableSerialization {

return concat([...calltableBytes, ...payloadBytes]);
}

/**
* Retrieves a specific field by its index.
*
* @param index - The index of the field to retrieve.
* @returns The field value as a `Uint8Array`, or `undefined` if the field is not found.
*/
getField(index: number): Uint8Array | undefined {
const field = this.fields.find(f => f.index === index);
return field ? field.value : undefined;
}

/**
* Deserializes a byte array into a `CalltableSerialization` object.
*
* This method reconstructs the call table and its fields from a serialized byte array.
*
* @param bytes - The serialized byte array.
* @returns A `CalltableSerialization` instance containing the deserialized fields.
* @throws An error if the byte array is invalid or missing required fields.
*/
static fromBytes(bytes: Uint8Array): CalltableSerialization {
const instance = new CalltableSerialization();
let offset = 0;

// Read the number of fields
const fieldCount = parseU32(bytes.slice(offset, offset + 4));
offset += 4;

const fields: Field[] = [];
for (let i = 0; i < fieldCount; i++) {
const index = parseU16(bytes.slice(offset, offset + 2));
offset += 2;
const fieldOffset = parseU32(bytes.slice(offset, offset + 4));
offset += 4;

// Initialize each field with an empty value
fields.push(new Field(index, fieldOffset, new Uint8Array()));
}

// Read the total payload size
const payloadSize = parseU32(bytes.slice(offset, offset + 4));
offset += 4;

// Extract field values based on their offsets
for (let i = 0; i < fields.length; i++) {
const start = fields[i].offset;
const end = i + 1 < fields.length ? fields[i + 1].offset : payloadSize;
fields[i].value = bytes.slice(offset + start, offset + end);
}

instance.fields = fields;
instance.currentOffset = payloadSize;
return instance;
}
}
Loading

0 comments on commit efc7f80

Please sign in to comment.