// Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 import { Bool, U128, U16, U256, U32, U64, U8, I8, I16, I32, I64, I128, I256 } from "./movePrimitives"; import { Serializable, Serializer, serializeEntryFunctionBytesCompat } from "../serializer"; import { Deserializable, Deserializer } from "../deserializer"; import { AnyNumber, HexInput, ScriptTransactionArgumentVariants } from "../../types"; import { Hex } from "../../core/hex"; import { EntryFunctionArgument, TransactionArgument } from "../../transactions/instances/transactionArgument"; /** * This class is the Aptos Typescript SDK representation of a Move `vector`, * where `T` represents either a primitive type (`bool`, `u8`, `u64`, ...) * or a BCS-serializable struct itself. * * It is a BCS-serializable, array-like type that contains an array of values of type `T`, * where `T` is a class that implements `Serializable`. * * The purpose of this class is to facilitate easy construction of BCS-serializable * Move `vector` types. * * @example * // in Move: `vector [1, 2, 3, 4];` * const vecOfU8s = new MoveVector([new U8(1), new U8(2), new U8(3), new U8(4)]); * // in Move: `std::bcs::to_bytes(vector [1, 2, 3, 4]);` * const bcsBytes = vecOfU8s.toUint8Array(); * * // vector> [ vector [1], vector [1, 2, 3, 4], vector [5, 6, 7, 8] ]; * const vecOfVecs = new MoveVector>([ * new MoveVector([new U8(1)]), * MoveVector.U8([1, 2, 3, 4]), * MoveVector.U8([5, 6, 7, 8]), * ]); * * // vector> [ std::option::some(1), std::option::some(2) ]; * const vecOfOptionU8s = new MoveVector>([ * MoveOption.U8(1), * MoveOption.U8(2), * ]); * * // vector [ std::string::utf8(b"hello"), std::string::utf8(b"world") ]; * const vecOfStrings = new MoveVector([new MoveString("hello"), new MoveString("world")]); * const vecOfStrings2 = MoveVector.MoveString(["hello", "world"]); * * @param values an Array of values where T is a class that implements Serializable * @returns a `MoveVector` with the values `values` * @group Implementation * @category BCS */ export class MoveVector extends Serializable implements TransactionArgument { public values: Array; /** * Initializes a new instance of the class with an optional value. * This constructor sets up the internal vector based on the provided value. * * @param values - The initial value to be stored in the vector, or null to initialize an empty vector. * @group Implementation * @category BCS */ constructor(values: Array) { super(); this.values = values; } /** * Serializes the current instance into a byte sequence suitable for entry functions. * This allows the data to be properly formatted for transmission or storage. * Uses serializeAsBytes when available, with a fallback for older Serializer versions. * * @param serializer - The serializer instance used to serialize the byte sequence. * @group Implementation * @category BCS */ serializeForEntryFunction(serializer: Serializer): void { serializeEntryFunctionBytesCompat(serializer, this); } /** * NOTE: This function will only work when the inner values in the `MoveVector` are `U8`s. * @param serializer * @group Implementation * @category BCS */ /** * Serialize the string as a fixed byte string without the length prefix for use in a script function. * @param serializer - The serializer used to convert the byte vector into a format suitable for a script function. * @group Implementation * @category BCS */ serializeForScriptFunction(serializer: Serializer): void { // This checks if the type of a non-empty vector is of type other than U8. If so, we use the Serialized // transaction argument type to serialize the argument. if (this.values[0] !== undefined && !(this.values[0] instanceof U8)) { const serialized = new Serialized(this.bcsToBytes()); serialized.serializeForScriptFunction(serializer); return; } serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U8Vector); serializer.serialize(this); } /** * Factory method to generate a MoveVector from a `number` or `undefined`. * * This method allows you to create a MoveVector that encapsulates a U8 value, enabling you to handle optional U8 values * effectively. * * @param values - The values used to fill the MoveVector. If `values` is undefined or null, the resulting MoveVector's * `.isSome()` method will return false. * @returns A MoveVector with an inner value `value`. * * @example * ```typescript * const v = MoveVector.U8([1, 2, 3, 4]); * ``` * @group Implementation * @category BCS */ static U8(values: Array | HexInput): MoveVector { let numbers: Array; if (Array.isArray(values) && values.length === 0) { // Handle empty array, since it won't have a "first value" numbers = []; } else if (Array.isArray(values) && typeof values[0] === "number") { numbers = values; } else if (typeof values === "string") { const hex = Hex.fromHexInput(values); numbers = Array.from(hex.toUint8Array()); } else if (values instanceof Uint8Array) { numbers = Array.from(values); } else { throw new Error("Invalid input type, must be an number[], Uint8Array, or hex string"); } return new MoveVector(numbers.map((v) => new U8(v))); } /** * Factory method to generate a MoveOption from a `number` or `null`. * * This method allows you to create a MoveVector that can either hold a U16 value or be empty. * * @param values - The value used to fill the MoveVector. If `value` is null or undefined, the resulting MoveVector's * `.isSome()` method will return false. * @returns A MoveVector with an inner value `value`. * @example * ```typescript * const v = MoveVector.U16([1, 2, 3, 4]); * ``` * @group Implementation * @category BCS */ static U16(values: Array): MoveVector { return new MoveVector(values.map((v) => new U16(v))); } /** * Factory method to generate a MoveVector from a `number` or `null`. * * This method allows you to create a MoveVector that can either hold a U32 value or be empty. * * @param values - The value used to fill the MoveVector. If `value` is null or undefined, * the resulting MoveVector's .isSome() method will return false. * @returns A MoveVector with an inner value `value`. * * @example * ``` * const v = MoveVector.U32([1, 2, 3, 4]); * ``` * @group Implementation * @category BCS */ static U32(values: Array): MoveVector { return new MoveVector(values.map((v) => new U32(v))); } /** * Factory method to generate a MoveVector from a number, bigint, or null/undefined. * This allows for the creation of an optional U64 value that can be checked for presence. * * @param values - The value used to fill the MoveVector. If `value` is undefined or null, the resulting MoveVector's * `.isSome()` method will return false. * @returns A MoveVector with an inner value `value`. * * @example * ```typescript * const v = MoveVector.U64([1, 2, 3, 4]); * ``` * @group Implementation * @category BCS */ static U64(values: Array): MoveVector { return new MoveVector(values.map((v) => new U64(v))); } /** * Factory method to generate a MoveVector from a number, bigint, or undefined. * * @param values - The value used to fill the MoveVector. If `value` is undefined, the resulting MoveVector's `.isSome()` * method will return false. * @returns A MoveVector with an inner value `value`. * * @example * ```typescript * const v = MoveVector.U128([1, 2, 3, 4]); * ``` * @group Implementation * @category BCS */ static U128(values: Array): MoveVector { return new MoveVector(values.map((v) => new U128(v))); } /** * Factory method to generate a MoveVector from a number, bigint, or null/undefined. * This allows for the creation of an optional U256 value, enabling checks for presence or absence of a value. * * @param values - The value used to fill the MoveVector. If `value` is undefined or null, * the resulting MoveVector's .isSome() method will return false. * @returns A MoveVector with an inner value `value`. * * @example * ```typescript * const v = MoveVector.U256([1, 2, 3, 4]); * ``` * @group Implementation * @category BCS */ static U256(values: Array): MoveVector { return new MoveVector(values.map((v) => new U256(v))); } /** * Factory method to generate a MoveVector from a `boolean` or `undefined`. * This method allows you to create an optional boolean value that can be used in various contexts where a boolean may or may * not be present. * * @param values - The value used to fill the MoveVector. If `value` is undefined, the resulting MoveVector's .isSome() method * will return false. * @returns A MoveVector with an inner value `value`. * * @example * * const v = MoveVector.Bool([true, false, true, false]); * @group Implementation * @category BCS */ static Bool(values: Array): MoveVector { return new MoveVector(values.map((v) => new Bool(v))); } /** * Factory method to generate a MoveVector from an array of numbers. * * @param values - The values used to fill the MoveVector. * @returns A MoveVector with the inner values. * * @example * ```typescript * const v = MoveVector.I8([-1, 0, 1, 127]); * ``` * @group Implementation * @category BCS */ static I8(values: Array): MoveVector { return new MoveVector(values.map((v) => new I8(v))); } /** * Factory method to generate a MoveVector from an array of numbers. * * @param values - The values used to fill the MoveVector. * @returns A MoveVector with the inner values. * * @example * ```typescript * const v = MoveVector.I16([-1, 0, 1, 32767]); * ``` * @group Implementation * @category BCS */ static I16(values: Array): MoveVector { return new MoveVector(values.map((v) => new I16(v))); } /** * Factory method to generate a MoveVector from an array of numbers. * * @param values - The values used to fill the MoveVector. * @returns A MoveVector with the inner values. * * @example * ```typescript * const v = MoveVector.I32([-1, 0, 1, 2147483647]); * ``` * @group Implementation * @category BCS */ static I32(values: Array): MoveVector { return new MoveVector(values.map((v) => new I32(v))); } /** * Factory method to generate a MoveVector from an array of numbers or bigints. * * @param values - The values used to fill the MoveVector. * @returns A MoveVector with the inner values. * * @example * ```typescript * const v = MoveVector.I64([-1n, 0n, 1n, 9223372036854775807n]); * ``` * @group Implementation * @category BCS */ static I64(values: Array): MoveVector { return new MoveVector(values.map((v) => new I64(v))); } /** * Factory method to generate a MoveVector from an array of numbers or bigints. * * @param values - The values used to fill the MoveVector. * @returns A MoveVector with the inner values. * * @example * ```typescript * const v = MoveVector.I128([-1n, 0n, 1n]); * ``` * @group Implementation * @category BCS */ static I128(values: Array): MoveVector { return new MoveVector(values.map((v) => new I128(v))); } /** * Factory method to generate a MoveVector from an array of numbers or bigints. * * @param values - The values used to fill the MoveVector. * @returns A MoveVector with the inner values. * * @example * ```typescript * const v = MoveVector.I256([-1n, 0n, 1n]); * ``` * @group Implementation * @category BCS */ static I256(values: Array): MoveVector { return new MoveVector(values.map((v) => new I256(v))); } /** * Factory method to generate a MoveVector from a `string` or `undefined`. * This function creates a MoveVector that encapsulates a MoveString if the provided value is not null or undefined. * * @param values - The value used to fill the MoveVector. If `value` is undefined, the resulting MoveVector's .isSome() method * will return false. * @returns A MoveVector with an inner value `value`. * * @example * const v = MoveVector.MoveString(["hello", "world"]); * @group Implementation * @category BCS */ static MoveString(values: Array): MoveVector { return new MoveVector(values.map((v) => new MoveString(v))); } /** * Serializes the current object using the provided serializer. * This function will serialize the value if it is present. * * @param serializer - The serializer instance used to perform the serialization. * @group Implementation * @category BCS */ serialize(serializer: Serializer): void; serialize(serializer: Serializer): void { serializer.serializeVector(this.values); } /** * Deserialize a MoveVector of type T, specifically where T is a Serializable and Deserializable type. * * NOTE: This only works with a depth of one. Generics will not work. * * NOTE: This will not work with types that aren't of the Serializable class. * * If you're looking for a more flexible deserialization function, you can use the deserializeVector function * in the Deserializer class. * * @example * const vec = MoveVector.deserialize(deserializer, U64); * @param deserializer the Deserializer instance to use, with bytes loaded into it already. * @param cls the class to typecast the input values to, must be a Serializable and Deserializable type. * @returns a MoveVector of the corresponding class T * * @group Implementation * @category BCS */ static deserialize( deserializer: Deserializer, cls: Deserializable, ): MoveVector { const length = deserializer.deserializeUleb128AsU32(); const values = new Array(); for (let i = 0; i < length; i += 1) { values.push(cls.deserialize(deserializer)); } return new MoveVector(values); } } /** * Represents a serialized data structure that encapsulates a byte array. * This class extends the Serializable class and provides methods for serialization * and deserialization of byte data, as well as converting to a MoveVector. * * @extends Serializable * @group Implementation * @category BCS */ export class Serialized extends Serializable implements TransactionArgument { public readonly value: Uint8Array; constructor(value: HexInput) { super(); this.value = Hex.fromHexInput(value).toUint8Array(); } serialize(serializer: Serializer): void { serializer.serializeBytes(this.value); } serializeForEntryFunction(serializer: Serializer): void { this.serialize(serializer); } serializeForScriptFunction(serializer: Serializer): void { serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.Serialized); this.serialize(serializer); } static deserialize(deserializer: Deserializer): Serialized { return new Serialized(deserializer.deserializeBytes()); } /** * Deserialize the bytecode into a MoveVector of the specified type. * This function allows you to convert serialized data into a usable MoveVector format. * * @param cls - The class type of the elements in the MoveVector. * @group Implementation * @category BCS */ toMoveVector(cls: Deserializable): MoveVector { const deserializer = new Deserializer(this.bcsToBytes()); deserializer.deserializeUleb128AsU32(); const vec = deserializer.deserializeVector(cls); return new MoveVector(vec); } } /** * Represents a string value that can be serialized and deserialized. * This class extends the Serializable base class and provides methods * for serializing the string in different contexts, such as for entry * functions and script functions. * * @extends Serializable * @group Implementation * @category BCS */ export class MoveString extends Serializable implements TransactionArgument { public value: string; constructor(value: string) { super(); this.value = value; } serialize(serializer: Serializer): void { serializer.serializeStr(this.value); } serializeForEntryFunction(serializer: Serializer): void { serializeEntryFunctionBytesCompat(serializer, this); } serializeForScriptFunction(serializer: Serializer): void { // Serialize the string as a fixed byte string, i.e., without the length prefix const textEncoder = new TextEncoder(); const fixedStringBytes = textEncoder.encode(this.value); // Put those bytes into a vector and serialize it as a script function argument const vectorU8 = MoveVector.U8(fixedStringBytes); vectorU8.serializeForScriptFunction(serializer); } static deserialize(deserializer: Deserializer): MoveString { return new MoveString(deserializer.deserializeStr()); } } export class MoveOption extends Serializable implements EntryFunctionArgument { private vec: MoveVector; public readonly value?: T; constructor(value?: T | null) { super(); if (typeof value !== "undefined" && value !== null) { this.vec = new MoveVector([value]); } else { this.vec = new MoveVector([]); } [this.value] = this.vec.values; } serializeForEntryFunction(serializer: Serializer): void { serializeEntryFunctionBytesCompat(serializer, this); } /** * Retrieves the inner value of the MoveOption. * * This method is inspired by Rust's `Option.unwrap()`, where attempting to unwrap a `None` value results in a panic. * This method will throw an error if the value is not present. * * @example * const option = new MoveOption(new Bool(true)); * const value = option.unwrap(); // Returns the Bool instance * * @throws {Error} Throws an error if the MoveOption does not contain a value. * * @returns {T} The contained value if present. * @group Implementation * @category BCS */ unwrap(): T { if (!this.isSome()) { throw new Error("Called unwrap on a MoveOption with no value"); } else { return this.vec.values[0]; } } /** * Check if the MoveOption has a value. * * @returns {boolean} Returns true if there is exactly one value in the MoveOption. * @group Implementation * @category BCS */ isSome(): boolean { return this.vec.values.length === 1; } serialize(serializer: Serializer): void { // serialize 0 or 1 // if 1, serialize the value this.vec.serialize(serializer); } /** * Factory method to generate a MoveOption from a `number` or `undefined`. * * @example * MoveOption.U8(1).isSome() === true; * MoveOption.U8().isSome() === false; * MoveOption.U8(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static U8(value?: number | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new U8(value) : undefined); } /** * Factory method to generate a MoveOption from a `number` or `undefined`. * * @example * MoveOption.U16(1).isSome() === true; * MoveOption.U16().isSome() === false; * MoveOption.U16(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static U16(value?: number | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new U16(value) : undefined); } /** * Factory method to generate a MoveOption from a `number` or `undefined`. * * @example * MoveOption.U32(1).isSome() === true; * MoveOption.U32().isSome() === false; * MoveOption.U32(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static U32(value?: number | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new U32(value) : undefined); } /** * Factory method to generate a MoveOption from a `number` or a `bigint` or `undefined`. * * @example * MoveOption.U64(1).isSome() === true; * MoveOption.U64().isSome() === false; * MoveOption.U64(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static U64(value?: AnyNumber | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new U64(value) : undefined); } /** * Factory method to generate a MoveOption from a `number` or a `bigint` or `undefined`. * * @example * MoveOption.U128(1).isSome() === true; * MoveOption.U128().isSome() === false; * MoveOption.U128(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static U128(value?: AnyNumber | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new U128(value) : undefined); } /** * Factory method to generate a MoveOption from a `number` or a `bigint` or `undefined`. * * @example * MoveOption.U256(1).isSome() === true; * MoveOption.U256().isSome() === false; * MoveOption.U256(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static U256(value?: AnyNumber | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new U256(value) : undefined); } /** * Factory method to generate a MoveOption from a `boolean` or `undefined`. * * @example * MoveOption.Bool(true).isSome() === true; * MoveOption.Bool().isSome() === false; * MoveOption.Bool(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static Bool(value?: boolean | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new Bool(value) : undefined); } /** * Factory method to generate a MoveOption from a `number` or `undefined`. * * @example * MoveOption.I8(1).isSome() === true; * MoveOption.I8().isSome() === false; * MoveOption.I8(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static I8(value?: number | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new I8(value) : undefined); } /** * Factory method to generate a MoveOption from a `number` or `undefined`. * * @example * MoveOption.I16(1).isSome() === true; * MoveOption.I16().isSome() === false; * MoveOption.I16(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static I16(value?: number | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new I16(value) : undefined); } /** * Factory method to generate a MoveOption from a `number` or `undefined`. * * @example * MoveOption.I32(1).isSome() === true; * MoveOption.I32().isSome() === false; * MoveOption.I32(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static I32(value?: number | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new I32(value) : undefined); } /** * Factory method to generate a MoveOption from a `number` or a `bigint` or `undefined`. * * @example * MoveOption.I64(1).isSome() === true; * MoveOption.I64().isSome() === false; * MoveOption.I64(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static I64(value?: AnyNumber | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new I64(value) : undefined); } /** * Factory method to generate a MoveOption from a `number` or a `bigint` or `undefined`. * * @example * MoveOption.I128(1).isSome() === true; * MoveOption.I128().isSome() === false; * MoveOption.I128(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static I128(value?: AnyNumber | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new I128(value) : undefined); } /** * Factory method to generate a MoveOption from a `number` or a `bigint` or `undefined`. * * @example * MoveOption.I256(1).isSome() === true; * MoveOption.I256().isSome() === false; * MoveOption.I256(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static I256(value?: AnyNumber | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new I256(value) : undefined); } /** * Factory method to generate a MoveOption from a `string` or `undefined`. * * @example * MoveOption.MoveString("hello").isSome() === true; * MoveOption.MoveString("").isSome() === true; * MoveOption.MoveString().isSome() === false; * MoveOption.MoveString(undefined).isSome() === false; * @param value the value used to fill the MoveOption. If `value` is undefined * the resulting MoveOption's .isSome() method will return false. * @returns a MoveOption with an inner value `value` * @group Implementation * @category BCS */ static MoveString(value?: string | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new MoveString(value) : undefined); } static deserialize( deserializer: Deserializer, cls: Deserializable, ): MoveOption { const vector = MoveVector.deserialize(deserializer, cls); return new MoveOption(vector.values[0]); } }