diff --git a/src/bcs/index.ts b/src/bcs/index.ts index 7c43334e0..19ae156bd 100644 --- a/src/bcs/index.ts +++ b/src/bcs/index.ts @@ -1,5 +1,5 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -export * from "./serializer"; export * from "./deserializer"; +export * from "./serializer"; diff --git a/src/bcs/serializable/entry-function-bytes.ts b/src/bcs/serializable/entry-function-bytes.ts new file mode 100644 index 000000000..ef154280e --- /dev/null +++ b/src/bcs/serializable/entry-function-bytes.ts @@ -0,0 +1,61 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +import { Serializer, Serializable } from "../serializer"; +import { Deserializer } from "../deserializer"; +import { FixedBytes } from "./fixed-bytes"; +import { EntryFunctionArgument } from "../../transactions/instances/transactionArgument"; +import { HexInput } from "../../types"; + +/** + * This class exists solely to represent a sequence of fixed bytes as a serialized entry function, because + * serializing an entry function appends a prefix that's *only* used for entry function arguments. + * + * NOTE: Attempting to use this class for a serialized script function will result in erroneous + * and unexpected behavior. + * + * If you wish to convert this class back to a TransactionArgument, you must know the type + * of the argument beforehand, and use the appropriate class to deserialize the bytes within + * an instance of this class. + */ +export class EntryFunctionBytes extends Serializable implements EntryFunctionArgument { + public readonly value: FixedBytes; + + private constructor(value: HexInput) { + super(); + this.value = new FixedBytes(value); + } + + // Note that to see the Move, BCS-serialized representation of the underlying fixed byte vector, + // we must not serialize the length prefix. + // + // In other words, this class is only used to represent a sequence of bytes that are already + // BCS-serialized as a type. To represent those bytes accurately, the BCS-serialized form is the same exact + // representation. + serialize(serializer: Serializer): void { + serializer.serialize(this.value); + } + + // When we serialize these bytes as an entry function argument, we need to + // serialize the length prefix. This essentially converts the underlying fixed byte vector to a type-agnostic + // byte vector to an `any` type. + // NOTE: This, and the lack of a `serializeForScriptFunction`, is the only meaningful difference between this + // class and FixedBytes. + serializeForEntryFunction(serializer: Serializer): void { + serializer.serializeU32AsUleb128(this.value.value.length); + serializer.serialize(this); + } + + /** + * The only way to create an instance of this class is to use this static method. + * + * This function should only be used when deserializing a sequence of EntryFunctionPayload arguments. + * @param deserializer the deserializer instance with the buffered bytes + * @param length the length of the bytes to deserialize + * @returns an instance of this class, which will now only be usable as an EntryFunctionArgument + */ + static deserialize(deserializer: Deserializer, length: number): EntryFunctionBytes { + const fixedBytes = FixedBytes.deserialize(deserializer, length); + return new EntryFunctionBytes(fixedBytes.value); + } +} diff --git a/src/bcs/serializable/fixed-bytes.ts b/src/bcs/serializable/fixed-bytes.ts index 7b51418ff..e62449e72 100644 --- a/src/bcs/serializable/fixed-bytes.ts +++ b/src/bcs/serializable/fixed-bytes.ts @@ -1,17 +1,34 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -import { Serializable, Serializer } from "../serializer"; +import { Serializer, Serializable } from "../serializer"; import { Deserializer } from "../deserializer"; import { HexInput } from "../../types"; -import { Hex } from "../../core"; +import { Hex } from "../../core/hex"; +import { TransactionArgument } from "../../transactions/instances/transactionArgument"; /** - * This class exists to represent a contiguous sequence of BCS bytes that when serialized - * do *not* prepend the length of the byte sequence at the beginning. + * This class exists to represent a contiguous sequence of already serialized BCS-bytes. * - * The main time to use this class is when you are passing around already BCS-serialized bytes - * that do not need to undergo another round of BCS serialization. + * It differs from most other Serializable classes in that its internal byte buffer is serialized to BCS + * bytes exactly as-is, without prepending the length of the bytes. + * + * If you want to write your own serialization function and pass the bytes as a transaction argument, + * you should use this class. + * + * This class is also more generally used to represent type-agnostic BCS bytes as a vector. + * + * An example of this is the bytes resulting from entry function arguments that have been serialized + * for an entry function. + * + * @example + * const yourCustomSerializedBytes = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + * const fixedBytes = new FixedBytes(yourCustomSerializedBytes); + * const payload = generateTransactionPayload({ + * function: "0xbeefcafe::your_module::your_function_that_requires_custom_serialization", + * type_arguments: [], + * arguments: [yourCustomBytes], + * }); * * For example, if you store each of the 32 bytes for an address as a U8 in a MoveVector, when you * serialize that MoveVector, it will be serialized to 33 bytes. If you solely want to pass around @@ -20,17 +37,9 @@ import { Hex } from "../../core"; * * @params value: HexInput representing a sequence of Uint8 bytes * @returns a Serializable FixedBytes instance, which when serialized, does not prepend the length of the bytes - * @example - * const address = AccountAddress.ONE; - * const bytes = address.bcsToBytes(); - * // bytes is the Move serialized version of an address - * // it has a fixed length, meaning it doesn't have a length at the beginning. - * const fixedBytes = new FixedBytes(bytes); - * // or, say, deserializing it from a sequence of bytes and you *do* know the length - * const fixedBytes = FixedBytes.deserialize(deserializer, 32); - * @see EntryFunction + * @see EntryFunctionBytes */ -export class FixedBytes extends Serializable { +export class FixedBytes extends Serializable implements TransactionArgument { public value: Uint8Array; constructor(value: HexInput) { @@ -42,6 +51,14 @@ export class FixedBytes extends Serializable { serializer.serializeFixedBytes(this.value); } + serializeForEntryFunction(serializer: Serializer): void { + serializer.serialize(this); + } + + serializeForScriptFunction(serializer: Serializer): void { + serializer.serialize(this); + } + static deserialize(deserializer: Deserializer, length: number): FixedBytes { const bytes = deserializer.deserializeFixedBytes(length); return new FixedBytes(bytes); diff --git a/src/bcs/serializable/move-primitives.ts b/src/bcs/serializable/move-primitives.ts index 9331f228e..b553be310 100644 --- a/src/bcs/serializable/move-primitives.ts +++ b/src/bcs/serializable/move-primitives.ts @@ -9,11 +9,12 @@ import { MAX_U8_NUMBER, MAX_U256_BIG_INT, } from "../consts"; -import { AnyNumber, Uint16, Uint32, Uint8 } from "../../types"; import { Deserializer } from "../deserializer"; import { Serializable, Serializer, ensureBoolean, validateNumberInRange } from "../serializer"; +import { TransactionArgument } from "../../transactions/instances/transactionArgument"; +import { AnyNumber, Uint16, Uint32, Uint8, ScriptTransactionArgumentVariants } from "../../types"; -export class Bool extends Serializable { +export class Bool extends Serializable implements TransactionArgument { public readonly value: boolean; constructor(value: boolean) { @@ -26,12 +27,22 @@ export class Bool extends Serializable { serializer.serializeBool(this.value); } + serializeForEntryFunction(serializer: Serializer): void { + const bcsBytes = this.bcsToBytes(); + serializer.serializeBytes(bcsBytes); + } + + serializeForScriptFunction(serializer: Serializer): void { + serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.Bool); + serializer.serialize(this); + } + static deserialize(deserializer: Deserializer): Bool { return new Bool(deserializer.deserializeBool()); } } -export class U8 extends Serializable { +export class U8 extends Serializable implements TransactionArgument { public readonly value: Uint8; constructor(value: Uint8) { @@ -44,12 +55,22 @@ export class U8 extends Serializable { serializer.serializeU8(this.value); } + serializeForEntryFunction(serializer: Serializer): void { + const bcsBytes = this.bcsToBytes(); + serializer.serializeBytes(bcsBytes); + } + + serializeForScriptFunction(serializer: Serializer): void { + serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U8); + serializer.serialize(this); + } + static deserialize(deserializer: Deserializer): U8 { return new U8(deserializer.deserializeU8()); } } -export class U16 extends Serializable { +export class U16 extends Serializable implements TransactionArgument { public readonly value: Uint16; constructor(value: Uint16) { @@ -62,12 +83,22 @@ export class U16 extends Serializable { serializer.serializeU16(this.value); } + serializeForEntryFunction(serializer: Serializer): void { + const bcsBytes = this.bcsToBytes(); + serializer.serializeBytes(bcsBytes); + } + + serializeForScriptFunction(serializer: Serializer): void { + serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U16); + serializer.serialize(this); + } + static deserialize(deserializer: Deserializer): U16 { return new U16(deserializer.deserializeU16()); } } -export class U32 extends Serializable { +export class U32 extends Serializable implements TransactionArgument { public readonly value: Uint32; constructor(value: Uint32) { @@ -80,12 +111,22 @@ export class U32 extends Serializable { serializer.serializeU32(this.value); } + serializeForEntryFunction(serializer: Serializer): void { + const bcsBytes = this.bcsToBytes(); + serializer.serializeBytes(bcsBytes); + } + + serializeForScriptFunction(serializer: Serializer): void { + serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U32); + serializer.serialize(this); + } + static deserialize(deserializer: Deserializer): U32 { return new U32(deserializer.deserializeU32()); } } -export class U64 extends Serializable { +export class U64 extends Serializable implements TransactionArgument { public readonly value: bigint; constructor(value: AnyNumber) { @@ -98,12 +139,22 @@ export class U64 extends Serializable { serializer.serializeU64(this.value); } + serializeForEntryFunction(serializer: Serializer): void { + const bcsBytes = this.bcsToBytes(); + serializer.serializeBytes(bcsBytes); + } + + serializeForScriptFunction(serializer: Serializer): void { + serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U64); + serializer.serialize(this); + } + static deserialize(deserializer: Deserializer): U64 { return new U64(deserializer.deserializeU64()); } } -export class U128 extends Serializable { +export class U128 extends Serializable implements TransactionArgument { public readonly value: bigint; constructor(value: AnyNumber) { @@ -116,12 +167,22 @@ export class U128 extends Serializable { serializer.serializeU128(this.value); } + serializeForEntryFunction(serializer: Serializer): void { + const bcsBytes = this.bcsToBytes(); + serializer.serializeBytes(bcsBytes); + } + + serializeForScriptFunction(serializer: Serializer): void { + serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U128); + serializer.serialize(this); + } + static deserialize(deserializer: Deserializer): U128 { return new U128(deserializer.deserializeU128()); } } -export class U256 extends Serializable { +export class U256 extends Serializable implements TransactionArgument { public readonly value: bigint; constructor(value: AnyNumber) { @@ -134,6 +195,16 @@ export class U256 extends Serializable { serializer.serializeU256(this.value); } + serializeForEntryFunction(serializer: Serializer): void { + const bcsBytes = this.bcsToBytes(); + serializer.serializeBytes(bcsBytes); + } + + serializeForScriptFunction(serializer: Serializer): void { + serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U256); + serializer.serialize(this); + } + static deserialize(deserializer: Deserializer): U256 { return new U256(deserializer.deserializeU256()); } diff --git a/src/bcs/serializable/move-structs.ts b/src/bcs/serializable/move-structs.ts index 0f4629999..657300161 100644 --- a/src/bcs/serializable/move-structs.ts +++ b/src/bcs/serializable/move-structs.ts @@ -1,11 +1,13 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 +import { Bool, U128, U16, U256, U32, U64, U8 } from "./move-primitives"; import { Serializable, Serializer } from "../serializer"; import { Deserializable, Deserializer } from "../deserializer"; -import { Bool, U128, U16, U256, U32, U64, U8 } from "./move-primitives"; -import { AnyNumber, HexInput } from "../../types"; -import { AccountAddress } from "../../core"; +import { AnyNumber, HexInput, ScriptTransactionArgumentVariants } from "../../types"; +import { Hex } from "../../core/hex"; +import { AccountAddress } from "../../core/account_address"; +import { EntryFunctionArgument, TransactionArgument } from "../../transactions/instances/transactionArgument"; /** * This class is the Aptos Typescript SDK representation of a Move `vector`, @@ -37,9 +39,9 @@ import { AccountAddress } from "../../core"; * MoveOption.U8(2), * ]); * - * // vector [ std::string::utf8(b"hello"), std::string::utf8(b"world") ]; + * // vector [ std::string::utf8(b"hello"), std::string::utf8(b"world") ]; * const vecOfStrings = new MoveVector([new MoveString("hello"), new MoveString("world")]); - * const vecOfStrings2 = MoveVector.String(["hello", "world"]); + * const vecOfStrings2 = MoveVector.MoveString(["hello", "world"]); * * // where MySerializableStruct is a class you've made that implements Serializable * const vecOfSerializableValues = new MoveVector([ @@ -50,7 +52,7 @@ import { AccountAddress } from "../../core"; * values: an Array of values where T is a class that implements Serializable * @returns a `MoveVector` with the values `values` */ -export class MoveVector extends Serializable { +export class MoveVector extends Serializable implements TransactionArgument { public values: Array; constructor(values: Array) { @@ -58,6 +60,26 @@ export class MoveVector extends Serializable { this.values = values; } + serializeForEntryFunction(serializer: Serializer): void { + const bcsBytes = this.bcsToBytes(); + serializer.serializeBytes(bcsBytes); + } + + /** + * NOTE: This function will only work when the inner values in the `MoveVector` are `U8`s. + * @param serializer + */ + serializeForScriptFunction(serializer: Serializer): void { + // runtime check to ensure that you can't serialize anything other than vector + // TODO: consider adding support for MoveString later? + const isU8 = this.values[0] instanceof U8; + if (!isU8) { + throw new Error("Script function arguments only accept u8 vectors"); + } + serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U8Vector); + serializer.serialize(this); + } + /** * Factory method to generate a MoveVector of U8s from an array of numbers. * @@ -66,8 +88,21 @@ export class MoveVector extends Serializable { * @params values: an array of `numbers` to convert to U8s * @returns a `MoveVector` */ - static U8(values: Array): MoveVector { - return new MoveVector(values.map((v) => new U8(v))); + static U8(values: Array | HexInput): MoveVector { + let numbers: Array; + + if (Array.isArray(values) && typeof values[0] === "number") { + numbers = values; + } else if (typeof values === "string") { + const hex = Hex.fromHexInput({ hexInput: values }); + numbers = Array.from(hex.toUint8Array()); + } else if (values instanceof Uint8Array) { + numbers = Array.from(values); + } else { + throw new Error("Invalid input type"); + } + + return new MoveVector(numbers.map((v) => new U8(v))); } /** @@ -146,11 +181,11 @@ export class MoveVector extends Serializable { * Factory method to generate a MoveVector of MoveStrings from an array of strings. * * @example - * const v = MoveVector.String(["hello", "world"]); + * const v = MoveVector.MoveString(["hello", "world"]); * @params values: an array of `numbers` to convert to MoveStrings * @returns a `MoveVector` */ - static String(values: Array): MoveVector { + static MoveString(values: Array): MoveVector { return new MoveVector(values.map((v) => new MoveString(v))); } @@ -184,7 +219,7 @@ export class MoveVector extends Serializable { } } -export class MoveString extends Serializable { +export class MoveString extends Serializable implements TransactionArgument { public value: string; constructor(value: string) { @@ -196,12 +231,23 @@ export class MoveString extends Serializable { serializer.serializeStr(this.value); } + serializeForEntryFunction(serializer: Serializer): void { + const bcsBytes = this.bcsToBytes(); + serializer.serializeBytes(bcsBytes); + } + + serializeForScriptFunction(serializer: Serializer): void { + // serialize the string, load it into a vector and serialize it as a script vector argument + const vectorU8 = MoveVector.U8(this.bcsToBytes()); + vectorU8.serializeForScriptFunction(serializer); + } + static deserialize(deserializer: Deserializer): MoveString { return new MoveString(deserializer.deserializeStr()); } } -export class MoveOption extends Serializable { +export class MoveOption extends Serializable implements EntryFunctionArgument { private vec: MoveVector; public readonly value?: T; @@ -217,6 +263,11 @@ export class MoveOption extends Serializable { [this.value] = this.vec.values; } + serializeForEntryFunction(serializer: Serializer): void { + const bcsBytes = this.bcsToBytes(); + serializer.serializeBytes(bcsBytes); + } + /** * Retrieves the inner value of the MoveOption. * @@ -361,15 +412,15 @@ export class MoveOption extends Serializable { * Factory method to generate a MoveOption from a `string` or `undefined`. * * @example - * MoveOption.String("hello").isSome() === true; - * MoveOption.String("").isSome() === true; - * MoveOption.String().isSome() === false; - * MoveOption.String(undefined).isSome() === false; + * MoveOption.MoveString("hello").isSome() === true; + * MoveOption.MoveString("").isSome() === true; + * MoveOption.MoveString().isSome() === false; + * MoveOption.MoveString(undefined).isSome() === false; * @params 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` */ - static String(value?: string | null): MoveOption { + static MoveString(value?: string | null): MoveOption { return new MoveOption(value !== null && value !== undefined ? new MoveString(value) : undefined); } @@ -379,7 +430,7 @@ export class MoveOption extends Serializable { } } -export class MoveObject extends Serializable { +export class MoveObject extends Serializable implements TransactionArgument { public value: AccountAddress; constructor(value: HexInput | AccountAddress) { @@ -396,6 +447,14 @@ export class MoveObject extends Serializable { serializer.serialize(this.value); } + serializeForEntryFunction(serializer: Serializer): void { + this.value.serializeForEntryFunction(serializer); + } + + serializeForScriptFunction(serializer: Serializer): void { + this.value.serializeForScriptFunction(serializer); + } + static deserialize(deserializer: Deserializer): MoveObject { const address = deserializer.deserialize(AccountAddress); return new MoveObject(address); diff --git a/src/bcs/serializer.ts b/src/bcs/serializer.ts index 4de550bbf..9ede18444 100644 --- a/src/bcs/serializer.ts +++ b/src/bcs/serializer.ts @@ -11,6 +11,7 @@ import { MAX_U256_BIG_INT, } from "./consts"; import { AnyNumber, Uint16, Uint32, Uint8 } from "../types"; +import { Hex } from "../core/hex"; // This class is intended to be used as a base class for all serializable types. // It can be used to facilitate composable serialization of a complex type and @@ -28,6 +29,15 @@ export abstract class Serializable { this.serialize(serializer); return serializer.toUint8Array(); } + + /** + * Helper function to get a value's BCS-serialized bytes as a Hex instance. + * @returns a Hex instance with the BCS-serialized bytes loaded into its underlying Uint8Array + */ + bcsToHex(): Hex { + const bcsBytes = this.bcsToBytes(); + return Hex.fromHexInput({ hexInput: bcsBytes }); + } } export class Serializer { diff --git a/src/core/account_address.ts b/src/core/account_address.ts index ae9c4bf2f..d4cb60256 100644 --- a/src/core/account_address.ts +++ b/src/core/account_address.ts @@ -2,9 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; -import { HexInput } from "../types"; +import { HexInput, ScriptTransactionArgumentVariants } from "../types"; import { ParsingError, ParsingResult } from "./common"; -import { Deserializer, Serializable, Serializer } from "../bcs"; +import { Serializable, Serializer } from "../bcs/serializer"; +import { Deserializer } from "../bcs/deserializer"; +import { TransactionArgument } from "../transactions/instances/transactionArgument"; /** * This enum is used to explain why an address was invalid. @@ -35,7 +37,7 @@ export enum AddressInvalidReason { * The comments in this class make frequent reference to the LONG and SHORT formats, * as well as "special" addresses. To learn what these refer to see AIP-40. */ -export class AccountAddress extends Serializable { +export class AccountAddress extends Serializable implements TransactionArgument { /* * This is the internal representation of an account address. */ @@ -181,6 +183,16 @@ export class AccountAddress extends Serializable { serializer.serializeFixedBytes(this.data); } + serializeForEntryFunction(serializer: Serializer): void { + const bcsBytes = this.bcsToBytes(); + serializer.serializeBytes(bcsBytes); + } + + serializeForScriptFunction(serializer: Serializer): void { + serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.Address); + serializer.serialize(this); + } + /** * Deserialize an AccountAddress from the byte buffer in a Deserializer instance. * @param deserializer The deserializer to deserialize the AccountAddress from. diff --git a/src/internal/transaction.ts b/src/internal/transaction.ts index bbdf7a668..a43b2afde 100644 --- a/src/internal/transaction.ts +++ b/src/internal/transaction.ts @@ -11,12 +11,12 @@ import { AptosConfig } from "../api/aptos_config"; import { AptosApiError, getAptosFullNode, paginateWithCursor } from "../client"; import { - AnyNumber, - GasEstimation, - HexInput, - PaginationArgs, - TransactionResponse, TransactionResponseType, + type AnyNumber, + type GasEstimation, + type HexInput, + type PaginationArgs, + type TransactionResponse, } from "../types"; import { DEFAULT_TXN_TIMEOUT_SEC } from "../utils/const"; import { sleep } from "../utils/helpers"; diff --git a/src/transactions/instances/chainId.ts b/src/transactions/instances/chainId.ts index 85e580f14..035e964a5 100644 --- a/src/transactions/instances/chainId.ts +++ b/src/transactions/instances/chainId.ts @@ -1,15 +1,17 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -import { Serializer, Deserializer } from "../../bcs"; +import { Serializer, Serializable } from "../../bcs/serializer"; +import { Deserializer } from "../../bcs/deserializer"; /** * Representation of a ChainId that can serialized and deserialized */ -export class ChainId { +export class ChainId extends Serializable { public readonly chainId: number; constructor(chainId: number) { + super(); this.chainId = chainId; } diff --git a/src/transactions/instances/identifier.ts b/src/transactions/instances/identifier.ts index 5801d0450..1346eb597 100644 --- a/src/transactions/instances/identifier.ts +++ b/src/transactions/instances/identifier.ts @@ -1,7 +1,8 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -import { Serializer, Deserializer, Serializable } from "../../bcs"; +import { Deserializer } from "../../bcs/deserializer"; +import { Serializable, Serializer } from "../../bcs/serializer"; /** * Representation of an Identifier that can serialized and deserialized. diff --git a/src/transactions/instances/index.ts b/src/transactions/instances/index.ts index 9c0ef3984..a253d247a 100644 --- a/src/transactions/instances/index.ts +++ b/src/transactions/instances/index.ts @@ -3,7 +3,7 @@ export * from "./chainId"; export * from "./rawTransaction"; -export * from "./scriptTransactionArguments"; export * from "./transactionPayload"; export * from "./moduleId"; export * from "./identifier"; +export * from "./transactionArgument"; diff --git a/src/transactions/instances/moduleId.ts b/src/transactions/instances/moduleId.ts index 3d973db49..683f2c82a 100644 --- a/src/transactions/instances/moduleId.ts +++ b/src/transactions/instances/moduleId.ts @@ -1,7 +1,8 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -import { Serializer, Deserializer } from "../../bcs"; +import { Serializable, Serializer } from "../../bcs/serializer"; +import { Deserializer } from "../../bcs/deserializer"; import { AccountAddress } from "../../core"; import { Identifier } from "./identifier"; @@ -9,7 +10,7 @@ import { Identifier } from "./identifier"; * Representation of a ModuleId that can serialized and deserialized * ModuleId means the module address (e.g "0x1") and the module name (e.g "coin") */ -export class ModuleId { +export class ModuleId extends Serializable { public readonly address: AccountAddress; public readonly name: Identifier; @@ -20,6 +21,7 @@ export class ModuleId { * @param name The module name under the "address". e.g "coin" */ constructor(address: AccountAddress, name: Identifier) { + super(); this.address = address; this.name = name; } diff --git a/src/transactions/instances/rawTransaction.ts b/src/transactions/instances/rawTransaction.ts index ddbd95920..59fc7f0d6 100644 --- a/src/transactions/instances/rawTransaction.ts +++ b/src/transactions/instances/rawTransaction.ts @@ -3,7 +3,8 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { Deserializer, Serializer, Serializable } from "../../bcs"; +import { Deserializer } from "../../bcs/deserializer"; +import { Serializable, Serializer } from "../../bcs/serializer"; import { AccountAddress } from "../../core"; import { TransactionVariants } from "../../types"; import { ChainId } from "./chainId"; diff --git a/src/transactions/instances/scriptTransactionArguments.ts b/src/transactions/instances/scriptTransactionArguments.ts deleted file mode 100644 index 3fc01b5a4..000000000 --- a/src/transactions/instances/scriptTransactionArguments.ts +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright © Aptos Foundation -// SPDX-License-Identifier: Apache-2.0 - -import { Serializer, Deserializer, Serializable } from "../../bcs"; -import { AccountAddress } from "../../core"; -import { ScriptTransactionArgumentVariants } from "../../types"; - -/** - * Representation of a Script Transaction Argument that can be serialized and deserialized - */ -export abstract class ScriptTransactionArgument extends Serializable { - /** - * Serialize a Script Transaction Argument - */ - abstract serialize(serializer: Serializer): void; - - /** - * Deserialize a Script Transaction Argument - */ - static deserialize(deserializer: Deserializer): ScriptTransactionArgument { - // index enum variant - const index = deserializer.deserializeUleb128AsU32(); - switch (index) { - case ScriptTransactionArgumentVariants.U8: - return ScriptTransactionArgumentU8.load(deserializer); - case ScriptTransactionArgumentVariants.U64: - return ScriptTransactionArgumentU64.load(deserializer); - case ScriptTransactionArgumentVariants.U128: - return ScriptTransactionArgumentU128.load(deserializer); - case ScriptTransactionArgumentVariants.Address: - return ScriptTransactionArgumentAddress.load(deserializer); - case ScriptTransactionArgumentVariants.U8Vector: - return ScriptTransactionArgumentU8Vector.load(deserializer); - case ScriptTransactionArgumentVariants.Bool: - return ScriptTransactionArgumentBool.load(deserializer); - case ScriptTransactionArgumentVariants.U16: - return ScriptTransactionArgumentU16.load(deserializer); - case ScriptTransactionArgumentVariants.U32: - return ScriptTransactionArgumentU32.load(deserializer); - case ScriptTransactionArgumentVariants.U256: - return ScriptTransactionArgumentU256.load(deserializer); - default: - throw new Error(`Unknown variant index for ScriptTransactionArgument: ${index}`); - } - } -} - -export class ScriptTransactionArgumentU8 extends ScriptTransactionArgument { - public readonly value: number; - - constructor(value: number) { - super(); - this.value = value; - } - - serialize(serializer: Serializer): void { - serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U8); - serializer.serializeU8(this.value); - } - - static load(deserializer: Deserializer): ScriptTransactionArgumentU8 { - const value = deserializer.deserializeU8(); - return new ScriptTransactionArgumentU8(value); - } -} - -export class ScriptTransactionArgumentU16 extends ScriptTransactionArgument { - public readonly value: number; - - constructor(value: number) { - super(); - this.value = value; - } - - serialize(serializer: Serializer): void { - serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U16); - serializer.serializeU16(this.value); - } - - static load(deserializer: Deserializer): ScriptTransactionArgumentU16 { - const value = deserializer.deserializeU16(); - return new ScriptTransactionArgumentU16(value); - } -} - -export class ScriptTransactionArgumentU32 extends ScriptTransactionArgument { - public readonly value: number; - - constructor(value: number) { - super(); - this.value = value; - } - - serialize(serializer: Serializer): void { - serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U32); - serializer.serializeU32(this.value); - } - - static load(deserializer: Deserializer): ScriptTransactionArgumentU32 { - const value = deserializer.deserializeU32(); - return new ScriptTransactionArgumentU32(value); - } -} - -export class ScriptTransactionArgumentU64 extends ScriptTransactionArgument { - public readonly value: bigint; - - constructor(value: bigint) { - super(); - this.value = value; - } - - serialize(serializer: Serializer): void { - serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U64); - serializer.serializeU64(this.value); - } - - static load(deserializer: Deserializer): ScriptTransactionArgumentU64 { - const value = deserializer.deserializeU64(); - return new ScriptTransactionArgumentU64(value); - } -} - -export class ScriptTransactionArgumentU128 extends ScriptTransactionArgument { - public readonly value: bigint; - - constructor(value: bigint) { - super(); - this.value = value; - } - - serialize(serializer: Serializer): void { - serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U128); - serializer.serializeU128(this.value); - } - - static load(deserializer: Deserializer): ScriptTransactionArgumentU128 { - const value = deserializer.deserializeU128(); - return new ScriptTransactionArgumentU128(value); - } -} - -export class ScriptTransactionArgumentU256 extends ScriptTransactionArgument { - public readonly value: bigint; - - constructor(value: bigint) { - super(); - this.value = value; - } - - serialize(serializer: Serializer): void { - serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U256); - serializer.serializeU256(this.value); - } - - static load(deserializer: Deserializer): ScriptTransactionArgumentU256 { - const value = deserializer.deserializeU256(); - return new ScriptTransactionArgumentU256(value); - } -} - -export class ScriptTransactionArgumentAddress extends ScriptTransactionArgument { - public readonly value: AccountAddress; - - constructor(value: AccountAddress) { - super(); - this.value = value; - } - - serialize(serializer: Serializer): void { - serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.Address); - this.value.serialize(serializer); - } - - static load(deserializer: Deserializer): ScriptTransactionArgumentAddress { - const value = AccountAddress.deserialize(deserializer); - return new ScriptTransactionArgumentAddress(value); - } -} - -export class ScriptTransactionArgumentU8Vector extends ScriptTransactionArgument { - public readonly value: Uint8Array; - - constructor(value: Uint8Array) { - super(); - this.value = value; - } - - serialize(serializer: Serializer): void { - serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.U8Vector); - serializer.serializeBytes(this.value); - } - - static load(deserializer: Deserializer): ScriptTransactionArgumentU8Vector { - const value = deserializer.deserializeBytes(); - return new ScriptTransactionArgumentU8Vector(value); - } -} - -export class ScriptTransactionArgumentBool extends ScriptTransactionArgument { - public readonly value: boolean; - - constructor(value: boolean) { - super(); - this.value = value; - } - - serialize(serializer: Serializer): void { - serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.Bool); - serializer.serializeBool(this.value); - } - - static load(deserializer: Deserializer): ScriptTransactionArgumentBool { - const value = deserializer.deserializeBool(); - return new ScriptTransactionArgumentBool(value); - } -} diff --git a/src/transactions/instances/signedTransaction.ts b/src/transactions/instances/signedTransaction.ts index 150df2937..8d4bb602e 100644 --- a/src/transactions/instances/signedTransaction.ts +++ b/src/transactions/instances/signedTransaction.ts @@ -3,7 +3,8 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { Serializer, Deserializer, Serializable } from "../../bcs"; +import { Deserializer } from "../../bcs/deserializer"; +import { Serializable, Serializer } from "../../bcs/serializer"; import { TransactionAuthenticator } from "../authenticator/transaction"; import { RawTransaction } from "./rawTransaction"; diff --git a/src/transactions/instances/transactionArgument.ts b/src/transactions/instances/transactionArgument.ts new file mode 100644 index 000000000..377066625 --- /dev/null +++ b/src/transactions/instances/transactionArgument.ts @@ -0,0 +1,34 @@ +import { Serializer } from "../../bcs/serializer"; +import { Hex } from "../../core/hex"; + +export interface TransactionArgument extends EntryFunctionArgument, ScriptFunctionArgument {} + +export interface EntryFunctionArgument { + /** + * Serialize an argument to BCS-serialized bytes. + */ + serialize(serializer: Serializer): void; + /** + * Serialize an argument as a type-agnostic, fixed byte sequence. The byte sequence contains + * the number of the following bytes followed by the BCS-serialized bytes for a typed argument. + */ + serializeForEntryFunction(serializer: Serializer): void; + + bcsToBytes(): Uint8Array; + bcsToHex(): Hex; +} +export interface ScriptFunctionArgument { + /** + * Serialize an argument to BCS-serialized bytes. + */ + serialize(serializer: Serializer): void; + /** + * Serialize an argument to BCS-serialized bytes as a type aware byte sequence. + * The byte sequence contains an enum variant index followed by the BCS-serialized + * bytes for a typed argument. + */ + serializeForScriptFunction(serializer: Serializer): void; + + bcsToBytes(): Uint8Array; + bcsToHex(): Hex; +} diff --git a/src/transactions/instances/transactionPayload.ts b/src/transactions/instances/transactionPayload.ts index 0a43d25ba..53e5a27d4 100644 --- a/src/transactions/instances/transactionPayload.ts +++ b/src/transactions/instances/transactionPayload.ts @@ -3,16 +3,47 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { Serializer, Deserializer, Serializable } from "../../bcs"; +import { Deserializer } from "../../bcs/deserializer"; +import { Serializable, Serializer } from "../../bcs/serializer"; import { AccountAddress } from "../../core"; import { Identifier } from "./identifier"; -import { ScriptTransactionArgument } from "./scriptTransactionArguments"; import { ModuleId } from "./moduleId"; -import { TransactionPayloadVariants } from "../../types"; +import { ScriptTransactionArgumentVariants, TransactionPayloadVariants } from "../../types"; import { TypeTag } from "../typeTag/typeTag"; -import { U8 } from "../../bcs/serializable/move-primitives"; +import type { EntryFunctionArgument, ScriptFunctionArgument, TransactionArgument } from "./transactionArgument"; +import { EntryFunctionBytes } from "../../bcs/serializable/entry-function-bytes"; +import { Bool, U128, U16, U256, U32, U64, U8 } from "../../bcs/serializable/move-primitives"; import { MoveVector } from "../../bcs/serializable/move-structs"; -import { FixedBytes } from "../../bcs/serializable/fixed-bytes"; + +/** + * Deserialize a Script Transaction Argument + */ +export function deserializeFromScriptArgument(deserializer: Deserializer): TransactionArgument { + // index enum variant + const index = deserializer.deserializeUleb128AsU32(); + switch (index) { + case ScriptTransactionArgumentVariants.U8: + return U8.deserialize(deserializer); + case ScriptTransactionArgumentVariants.U64: + return U64.deserialize(deserializer); + case ScriptTransactionArgumentVariants.U128: + return U128.deserialize(deserializer); + case ScriptTransactionArgumentVariants.Address: + return AccountAddress.deserialize(deserializer); + case ScriptTransactionArgumentVariants.U8Vector: + return MoveVector.deserialize(deserializer, U8); + case ScriptTransactionArgumentVariants.Bool: + return Bool.deserialize(deserializer); + case ScriptTransactionArgumentVariants.U16: + return U16.deserialize(deserializer); + case ScriptTransactionArgumentVariants.U32: + return U32.deserialize(deserializer); + case ScriptTransactionArgumentVariants.U256: + return U256.deserialize(deserializer); + default: + throw new Error(`Unknown variant index for ScriptTransactionArgument: ${index}`); + } +} /** * Representation of the supported Transaction Payload @@ -119,7 +150,7 @@ export class EntryFunction { public readonly type_args: Array; - public readonly args: Array; + public readonly args: Array; /** * Contains the payload to run a function within a module. @@ -140,7 +171,12 @@ export class EntryFunction { * public entry fun transfer(from: &signer, to: address, amount: u64) * ``` */ - constructor(module_name: ModuleId, function_name: Identifier, type_args: Array, args: Array) { + constructor( + module_name: ModuleId, + function_name: Identifier, + type_args: Array, + args: Array, + ) { this.module_name = module_name; this.function_name = function_name; this.type_args = type_args; @@ -172,7 +208,7 @@ export class EntryFunction { module_name: `${string}::${string}`, function_name: string, type_args: Array, - args: Array, + args: Array, ): EntryFunction { return new EntryFunction(ModuleId.fromStr(module_name), new Identifier(function_name), type_args, args); } @@ -181,20 +217,20 @@ export class EntryFunction { this.module_name.serialize(serializer); this.function_name.serialize(serializer); serializer.serializeVector(this.type_args); - serializer.serializeU32AsUleb128(this.args.length); - this.args.forEach((item: Serializable) => { - const bytes = item.bcsToBytes(); - serializer.serializeBytes(bytes); + this.args.forEach((item: EntryFunctionArgument) => { + item.serializeForEntryFunction(serializer); }); } /** - * Deserializes an entry function payload with the arguments represented as FixedBytes instances. - * @see FixedBytes + * Deserializes an entry function payload with the arguments represented as EntryFunctionBytes instances. + * @see EntryFunctionBytes * * NOTE: When you deserialize an EntryFunction payload with this method, the entry function - * arguments are populated as type-agnostic, raw fixed bytes in the form of the FixedBytes class. + * arguments are populated into the deserialized instance as type-agnostic, raw fixed bytes + * in the form of the EntryFunctionBytes class. + * * In order to correctly deserialize these arguments as their actual type representations, you * must know the types of the arguments beforehand and deserialize them yourself individually. * @@ -211,16 +247,14 @@ export class EntryFunction { const type_args = deserializer.deserializeVector(TypeTag); const length = deserializer.deserializeUleb128AsU32(); - const list: Array = new Array>(); + const args: Array = new Array(); for (let i = 0; i < length; i += 1) { const fixedBytesLength = deserializer.deserializeUleb128AsU32(); - const fixedBytes = FixedBytes.deserialize(deserializer, fixedBytesLength); - list.push(fixedBytes); + const fixedBytes = EntryFunctionBytes.deserialize(deserializer, fixedBytesLength); + args.push(fixedBytes); } - const args = list; - return new EntryFunction(module_name, function_name, type_args, args); } } @@ -242,7 +276,7 @@ export class Script { /** * The arguments that the bytecode function requires. */ - public readonly args: Array; + public readonly args: Array; /** * Scripts contain the Move bytecodes payload that can be submitted to Aptos chain for execution. @@ -263,7 +297,7 @@ export class Script { * public(script) fun transfer(from: &signer, to: address, amount: u64,) * ``` */ - constructor(bytecode: Uint8Array, type_args: Array, args: Array) { + constructor(bytecode: Uint8Array, type_args: Array, args: Array) { this.bytecode = bytecode; this.type_args = type_args; this.args = args; @@ -272,13 +306,24 @@ export class Script { serialize(serializer: Serializer): void { serializer.serializeBytes(this.bytecode); serializer.serializeVector(this.type_args); - serializer.serializeVector(this.args); + serializer.serializeU32AsUleb128(this.args.length); + this.args.forEach((item: ScriptFunctionArgument) => { + item.serializeForScriptFunction(serializer); + }); } static deserialize(deserializer: Deserializer): Script { const bytecode = deserializer.deserializeBytes(); const type_args = deserializer.deserializeVector(TypeTag); - const args = deserializer.deserializeVector(ScriptTransactionArgument); + const length = deserializer.deserializeUleb128AsU32(); + const args = new Array(); + for (let i = 0; i < length; i += 1) { + // Note that we deserialize directly to the Move value, not its Script argument representation. + // We are abstracting away the Script argument representation because knowing about it is + // functionally useless. + const scriptArgument = deserializeFromScriptArgument(deserializer); + args.push(scriptArgument); + } return new Script(bytecode, type_args, args); } } diff --git a/src/transactions/transaction_builder/transaction_builder.ts b/src/transactions/transaction_builder/transaction_builder.ts index 6e0d268cc..86623becd 100644 --- a/src/transactions/transaction_builder/transaction_builder.ts +++ b/src/transactions/transaction_builder/transaction_builder.ts @@ -9,7 +9,7 @@ import { sha3_256 as sha3Hash } from "@noble/hashes/sha3"; import { hexToBytes } from "@noble/hashes/utils"; import { AptosConfig } from "../../api/aptos_config"; -import { Deserializer } from "../../bcs"; +import { Deserializer } from "../../bcs/deserializer"; import { AccountAddress } from "../../core"; import { Account } from "../../core/account"; import { Ed25519PublicKey, Ed25519Signature } from "../../core/crypto/ed25519"; diff --git a/src/transactions/typeTag/typeTag.ts b/src/transactions/typeTag/typeTag.ts index a6459b10c..0f96fb37c 100644 --- a/src/transactions/typeTag/typeTag.ts +++ b/src/transactions/typeTag/typeTag.ts @@ -5,7 +5,8 @@ /* eslint-disable class-methods-use-this */ /* eslint-disable max-classes-per-file */ import { AccountAddress } from "../../core"; -import { Deserializer, Serializable, Serializer } from "../../bcs"; +import { Deserializer } from "../../bcs/deserializer"; +import { Serializable, Serializer } from "../../bcs/serializer"; import { Identifier } from "../instances/identifier"; import { TypeTagVariants } from "../../types"; @@ -235,12 +236,8 @@ export class StructTag extends Serializable { } } -export const stringStructTag = new StructTag( - AccountAddress.ONE, - new Identifier("string"), - new Identifier("String"), - [], -); +export const stringStructTag = () => + new StructTag(AccountAddress.ONE, new Identifier("string"), new Identifier("String"), []); export function optionStructTag(typeArg: TypeTag): StructTag { return new StructTag(AccountAddress.ONE, new Identifier("option"), new Identifier("Option"), [typeArg]); @@ -352,7 +349,7 @@ export class TypeTagParser { return new TypeTagVector(res); } if (tokenVal === "string") { - return new TypeTagStruct(stringStructTag); + return new TypeTagStruct(stringStructTag()); } if (tokenTy === "IDENT" && (tokenVal.startsWith("0x") || tokenVal.startsWith("0X"))) { const address = AccountAddress.fromHexInput({ input: tokenVal }); diff --git a/src/transactions/types.ts b/src/transactions/types.ts index 21ce5ae6e..6638e5e30 100644 --- a/src/transactions/types.ts +++ b/src/transactions/types.ts @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 import { AptosConfig } from "../api/aptos_config"; -import { Serializable } from "../bcs"; import { AccountAddress } from "../core"; import { PublicKey } from "../core/crypto/asymmetric_crypto"; import { HexInput, MoveStructType } from "../types"; @@ -10,12 +9,37 @@ import { MultiAgentRawTransaction, FeePayerRawTransaction, RawTransaction, - ScriptTransactionArgument, TransactionPayloadEntryFunction, TransactionPayloadMultisig, TransactionPayloadScript, } from "./instances"; import { TypeTag } from "./typeTag/typeTag"; +import { MoveObject, MoveOption, MoveVector } from "../bcs/serializable/move-structs"; +import { Bool, U128, U16, U256, U32, U64, U8 } from "../bcs/serializable/move-primitives"; + +export type EntryFunctionArgumentTypes = + | Bool + | U8 + | U16 + | U32 + | U64 + | U128 + | U256 + | AccountAddress + | MoveObject + | MoveVector + | MoveOption; +export type ScriptFunctionArgumentTypes = + | Bool + | U8 + | U16 + | U32 + | U64 + | U128 + | U256 + | AccountAddress + | MoveObject + | MoveVector; /** * Type that holds all raw transaction instances Aptos SDK supports @@ -54,7 +78,7 @@ export type GenerateTransactionPayloadData = EntryFunctionData | ScriptData | Mu export type EntryFunctionData = { function: MoveStructType; type_arguments: Array; - arguments: Array; + arguments: Array; }; /** @@ -70,7 +94,7 @@ export type MultiSigData = { export type ScriptData = { bytecode: string; type_arguments: Array; - arguments: Array; + arguments: Array; }; /** diff --git a/tests/e2e/api/transaction_submission.test.ts b/tests/e2e/api/transaction_submission.test.ts index 068a37a2d..7dd56268e 100644 --- a/tests/e2e/api/transaction_submission.test.ts +++ b/tests/e2e/api/transaction_submission.test.ts @@ -5,10 +5,8 @@ import { Account, AptosConfig, Ed25519PrivateKey, Network, Aptos, Deserializer } import { U64 } from "../../../src/bcs/serializable/move-primitives"; import { MoveObject } from "../../../src/bcs/serializable/move-structs"; import { AccountAuthenticator, AccountAuthenticatorEd25519 } from "../../../src/transactions/authenticator/account"; +import { RawTransaction } from "../../../src/transactions/instances"; import { - RawTransaction, - ScriptTransactionArgumentAddress, - ScriptTransactionArgumentU64, TransactionPayloadEntryFunction, TransactionPayloadMultisig, TransactionPayloadScript, @@ -111,11 +109,11 @@ describe("transaction submission", () => { "a11ceb0b060000000701000402040a030e18042608052e4307713e08af01200000000101020401000100030800010403040100010505060100010607040100010708060100000201020202030207060c060c0303050503030b000108010b000108010b0001080101080102060c03010b0001090002070b000109000b000109000002070b000109000302050b000109000a6170746f735f636f696e04636f696e04436f696e094170746f73436f696e087769746864726177056d657267650765787472616374076465706f73697400000000000000000000000000000000000000000000000000000000000000010000011a0b000a0238000c070b010a0338000c080d070b0838010d070b020b03160b061738020c090b040b0738030b050b09380302", type_arguments: [], arguments: [ - new ScriptTransactionArgumentU64(BigInt(100)), - new ScriptTransactionArgumentU64(BigInt(200)), - new ScriptTransactionArgumentAddress(bob.accountAddress), - new ScriptTransactionArgumentAddress(alice.accountAddress), - new ScriptTransactionArgumentU64(BigInt(50)), + new U64(BigInt(100)), + new U64(BigInt(200)), + bob.accountAddress, + alice.accountAddress, + new U64(BigInt(50)), ], }, }); @@ -232,11 +230,11 @@ describe("transaction submission", () => { "a11ceb0b060000000701000402040a030e18042608052e4307713e08af01200000000101020401000100030800010403040100010505060100010607040100010708060100000201020202030207060c060c0303050503030b000108010b000108010b0001080101080102060c03010b0001090002070b000109000b000109000002070b000109000302050b000109000a6170746f735f636f696e04636f696e04436f696e094170746f73436f696e087769746864726177056d657267650765787472616374076465706f73697400000000000000000000000000000000000000000000000000000000000000010000011a0b000a0238000c070b010a0338000c080d070b0838010d070b020b03160b061738020c090b040b0738030b050b09380302", type_arguments: [], arguments: [ - new ScriptTransactionArgumentU64(BigInt(100)), - new ScriptTransactionArgumentU64(BigInt(200)), - new ScriptTransactionArgumentAddress(bob.accountAddress), - new ScriptTransactionArgumentAddress(alice.accountAddress), - new ScriptTransactionArgumentU64(BigInt(50)), + new U64(BigInt(100)), + new U64(BigInt(200)), + bob.accountAddress, + alice.accountAddress, + new U64(BigInt(50)), ], }, }); diff --git a/tests/unit/bcs-helper.test.ts b/tests/unit/bcs-helper.test.ts index 1894e7cce..790389114 100644 --- a/tests/unit/bcs-helper.test.ts +++ b/tests/unit/bcs-helper.test.ts @@ -177,7 +177,7 @@ describe("Tests for the Serializable class", () => { MoveOption.U128(undefined), MoveOption.U256(undefined), MoveOption.Bool(undefined), - MoveOption.String(undefined), + MoveOption.MoveString(undefined), ]; const noneBytes = noneOptionValues.map((_) => new Uint8Array([0])); @@ -238,7 +238,7 @@ describe("Tests for the Serializable class", () => { testSerdeAndUnwrap(MoveOption.U128, U128); testSerdeAndUnwrap(MoveOption.U256, U256); testSerdeAndUnwrap(MoveOption.Bool, Bool); - testSerdeAndUnwrap(MoveOption.String, MoveString); + testSerdeAndUnwrap(MoveOption.MoveString, MoveString); }); it("serializes and deserializes a Vector of MoveOption types correctly", () => { @@ -345,7 +345,7 @@ describe("Tests for the Serializable class", () => { const u64VectorFrom = MoveVector.U64([1, 2, 3]); const u128VectorFrom = MoveVector.U128([1, 2, 3]); const u256VectorFrom = MoveVector.U256([1, 2, 3]); - const stringVectorFrom = MoveVector.String(["abc", "def", "ghi"]); + const stringVectorFrom = MoveVector.MoveString(["abc", "def", "ghi"]); const boolVectorBytes = new Uint8Array([3, 1, 0, 1]); const u8VectorBytes = new Uint8Array([3, 1, 2, 3]); @@ -389,7 +389,7 @@ describe("Tests for the Serializable class", () => { const u256Vector = new MoveVector([new U256(1), new U256(2), new U256(3)]); const u256VectorFrom = MoveVector.U256([1, 2, 3]); const stringVector = new MoveVector([new MoveString("abc"), new MoveString("def"), new MoveString("ghi")]); - const stringVectorFrom = MoveVector.String(["abc", "def", "ghi"]); + const stringVectorFrom = MoveVector.MoveString(["abc", "def", "ghi"]); expect(boolVector.bcsToBytes()).toEqual(boolVectorFrom.bcsToBytes()); expect(u8Vector.bcsToBytes()).toEqual(u8VectorFrom.bcsToBytes()); @@ -490,7 +490,7 @@ describe("Tests for the Serializable class", () => { MoveVector.U64([1, 2, 3]), MoveVector.U128([1, 2, 3]), MoveVector.U256([1, 2, 3]), - MoveVector.String(["abc", "def", "ghi"]), + MoveVector.MoveString(["abc", "def", "ghi"]), new MoveOption(new Bool(true)), new MoveOption(), new MoveOption(new MoveString("abc")), @@ -548,4 +548,43 @@ describe("Tests for the Serializable class", () => { const deserializedFixedBytes = FixedBytes.deserialize(deserializer, AccountAddress.LENGTH); expect(deserializedFixedBytes.value).toEqual(address.data); }); + + describe("MoveVector.U8 factory method tests", () => { + it("creates a MoveVector.U8 correctly", () => { + const vec = MoveVector.U8([1, 2, 3]); + const vecFromString = MoveVector.U8("0x010203"); + const vecFromUint8Array = MoveVector.U8(new Uint8Array([1, 2, 3])); + expect(vec.bcsToBytes()).toEqual(vecFromString.bcsToBytes()); + expect(vec.bcsToBytes()).toEqual(vecFromUint8Array.bcsToBytes()); + }); + + it("serializes and deserializes a MoveVector.U8 from various input types correctly", () => { + const vec = MoveVector.U8([1, 2, 3]); + const vecFromString = MoveVector.U8("0x010203"); + const vecFromUint8Array = MoveVector.U8(new Uint8Array([1, 2, 3])); + const deserializedVec = MoveVector.deserialize(new Deserializer(vec.bcsToBytes()), U8); + const deserializedVecFromString = MoveVector.deserialize(new Deserializer(vecFromString.bcsToBytes()), U8); + const deserializedVecFromUint8Array = MoveVector.deserialize( + new Deserializer(vecFromUint8Array.bcsToBytes()), + U8, + ); + expect(deserializedVec.values.map((v) => v.value)).toEqual(vec.values.map((v) => v.value)); + expect(deserializedVecFromString.values.map((v) => v.value)).toEqual(vec.values.map((v) => v.value)); + expect(deserializedVecFromUint8Array.values.map((v) => v.value)).toEqual(vec.values.map((v) => v.value)); + }); + + it("throws an error when trying to create a MoveVector.U8 from an invalid hex string", () => { + expect(() => MoveVector.U8("0x0102030")).toThrow(); + expect(() => MoveVector.U8("0xgg")).toThrow(); + // TODO: Add input validation to HexInput for truncating non-hex values like below + // expect(() => MoveVector.U8("asdf")).toThrow(); + expect(() => MoveVector.U8("gg")).toThrow(); + }); + + it("throws an error when trying to create a MoveVector.U8 from an invalid input type", () => { + expect(() => MoveVector.U8({} as any)).toThrow(); + expect(() => MoveVector.U8(["01", "02", "03"] as any)).toThrow(); + expect(() => MoveVector.U8([BigInt(1)] as any)).toThrow(); + }); + }); }); diff --git a/tests/unit/script_transaction_arguments.test.ts b/tests/unit/script_transaction_arguments.test.ts new file mode 100644 index 000000000..cb1d676e8 --- /dev/null +++ b/tests/unit/script_transaction_arguments.test.ts @@ -0,0 +1,97 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +import { Serializer, Deserializer } from "../../src/bcs"; +import { AccountAddress } from "../../src/core"; +import { Bool, U128, U16, U256, U32, U64, U8 } from "../../src/bcs/serializable/move-primitives"; +import { MoveVector } from "../../src/bcs/serializable/move-structs"; +import { ScriptFunctionArgument, deserializeFromScriptArgument } from "../../src/transactions/instances"; + +describe("Tests for the script transaction argument class", () => { + let serializer: Serializer; + let scriptU8Bytes: Uint8Array; + let scriptU16Bytes: Uint8Array; + let scriptU32Bytes: Uint8Array; + let scriptU64Bytes: Uint8Array; + let scriptU128Bytes: Uint8Array; + let scriptU256Bytes: Uint8Array; + let scriptBoolBytes: Uint8Array; + let scriptAddressBytes: Uint8Array; + let scriptVectorU8Bytes: Uint8Array; + + beforeEach(() => { + serializer = new Serializer(); + scriptU8Bytes = new Uint8Array([0, 1]); + scriptU16Bytes = new Uint8Array([6, 2, 0]); + scriptU32Bytes = new Uint8Array([7, 3, 0, 0, 0]); + scriptU64Bytes = new Uint8Array([1, 4, 0, 0, 0, 0, 0, 0, 0]); + scriptU128Bytes = new Uint8Array([2, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + scriptU256Bytes = new Uint8Array([ + 8, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + scriptBoolBytes = new Uint8Array([5, 0]); + scriptAddressBytes = new Uint8Array([3, ...AccountAddress.FOUR.data]); + scriptVectorU8Bytes = new Uint8Array([4, 5, 1, 2, 3, 4, 5]); + }); + + it("should serialize all types of ScriptTransactionArguments correctly", () => { + const validateBytes = (input: ScriptFunctionArgument, expectedOutput: Uint8Array) => { + const serializer = new Serializer(); + input.serializeForScriptFunction(serializer); + const serializedBytes = serializer.toUint8Array(); + expect(serializedBytes).toEqual(expectedOutput); + }; + validateBytes(new U8(1), scriptU8Bytes); + validateBytes(new U16(2), scriptU16Bytes); + validateBytes(new U32(3), scriptU32Bytes); + validateBytes(new U64(4), scriptU64Bytes); + validateBytes(new U128(5), scriptU128Bytes); + validateBytes(new U256(6), scriptU256Bytes); + validateBytes(new Bool(false), scriptBoolBytes); + validateBytes(AccountAddress.FOUR, scriptAddressBytes); + validateBytes(MoveVector.U8([1, 2, 3, 4, 5]), scriptVectorU8Bytes); + }); + + const deserializeAsScriptArg = (input: ScriptFunctionArgument) => { + serializer = new Serializer(); + input.serializeForScriptFunction(serializer); + const deserializer = new Deserializer(serializer.toUint8Array()); + return deserializeFromScriptArgument(deserializer); + }; + + it("should deserialize all types of ScriptTransactionArguments correctly", () => { + const scriptArgU8 = deserializeAsScriptArg(new U8(1)) as U8; + const scriptArgU16 = deserializeAsScriptArg(new U16(2)) as U16; + const scriptArgU32 = deserializeAsScriptArg(new U32(3)) as U32; + const scriptArgU64 = deserializeAsScriptArg(new U64(4)) as U64; + const scriptArgU128 = deserializeAsScriptArg(new U128(5)) as U128; + const scriptArgU256 = deserializeAsScriptArg(new U256(6)) as U256; + const scriptArgBool = deserializeAsScriptArg(new Bool(false)) as Bool; + const scriptArgAddress = deserializeAsScriptArg(AccountAddress.FOUR) as AccountAddress; + const scriptArgU8Vector = deserializeAsScriptArg(MoveVector.U8([1, 2, 3, 4, 5])) as MoveVector; + + expect(scriptArgU8.value).toEqual(1); + expect(scriptArgU16.value).toEqual(2); + expect(scriptArgU32.value).toEqual(3); + expect(scriptArgU64.value).toEqual(4n); + expect(scriptArgU128.value).toEqual(5n); + expect(scriptArgU256.value).toEqual(6n); + expect(scriptArgBool.value).toEqual(false); + expect(scriptArgAddress.data).toEqual(AccountAddress.FOUR.data); + expect(scriptArgU8Vector.values.map((v) => v.value)).toEqual([1, 2, 3, 4, 5]); + }); + + it("should convert all Move primitives to script transaction arguments correctly", () => { + expect(deserializeAsScriptArg(new U8(1)) instanceof U8).toBe(true); + expect(deserializeAsScriptArg(new U16(2)) instanceof U16).toBe(true); + expect(deserializeAsScriptArg(new U32(3)) instanceof U32).toBe(true); + expect(deserializeAsScriptArg(new U64(4)) instanceof U64).toBe(true); + expect(deserializeAsScriptArg(new U128(5)) instanceof U128).toBe(true); + expect(deserializeAsScriptArg(new U256(6)) instanceof U256).toBe(true); + expect(deserializeAsScriptArg(new Bool(false)) instanceof Bool).toBe(true); + expect(deserializeAsScriptArg(new AccountAddress(AccountAddress.FOUR)) instanceof AccountAddress).toBe(true); + expect(deserializeAsScriptArg(MoveVector.U8([1, 2, 3, 4, 5])) instanceof MoveVector).toBe(true); + const deserializedVectorU8 = deserializeAsScriptArg(MoveVector.U8([1, 2, 3, 4, 5])) as MoveVector; + expect(deserializedVectorU8.values.every((v) => v instanceof U8)).toBe(true); + }); +}); diff --git a/tests/unit/transaction_builder.test.ts b/tests/unit/transaction_builder.test.ts index 94705f831..4446f6815 100644 --- a/tests/unit/transaction_builder.test.ts +++ b/tests/unit/transaction_builder.test.ts @@ -7,8 +7,6 @@ import { FeePayerRawTransaction, MultiAgentRawTransaction, RawTransaction, - ScriptTransactionArgumentAddress, - ScriptTransactionArgumentU64, TransactionPayloadEntryFunction, TransactionPayloadMultisig, TransactionPayloadScript, @@ -75,11 +73,11 @@ describe("transaction builder", () => { "a11ceb0b060000000701000402040a030e18042608052e4307713e08af01200000000101020401000100030800010403040100010505060100010607040100010708060100000201020202030207060c060c0303050503030b000108010b000108010b0001080101080102060c03010b0001090002070b000109000b000109000002070b000109000302050b000109000a6170746f735f636f696e04636f696e04436f696e094170746f73436f696e087769746864726177056d657267650765787472616374076465706f73697400000000000000000000000000000000000000000000000000000000000000010000011a0b000a0238000c070b010a0338000c080d070b0838010d070b020b03160b061738020c090b040b0738030b050b09380302", type_arguments: [], arguments: [ - new ScriptTransactionArgumentU64(BigInt(100)), - new ScriptTransactionArgumentU64(BigInt(200)), - new ScriptTransactionArgumentAddress(Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress), - new ScriptTransactionArgumentAddress(Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress), - new ScriptTransactionArgumentU64(BigInt(50)), + new U64(100), + new U64(200), + Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress, + Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress, + new U64(50), ], }); const rawTxn = await generateRawTransaction({ @@ -149,11 +147,11 @@ describe("transaction builder", () => { "a11ceb0b060000000701000402040a030e18042608052e4307713e08af01200000000101020401000100030800010403040100010505060100010607040100010708060100000201020202030207060c060c0303050503030b000108010b000108010b0001080101080102060c03010b0001090002070b000109000b000109000002070b000109000302050b000109000a6170746f735f636f696e04636f696e04436f696e094170746f73436f696e087769746864726177056d657267650765787472616374076465706f73697400000000000000000000000000000000000000000000000000000000000000010000011a0b000a0238000c070b010a0338000c080d070b0838010d070b020b03160b061738020c090b040b0738030b050b09380302", type_arguments: [], arguments: [ - new ScriptTransactionArgumentU64(BigInt(100)), - new ScriptTransactionArgumentU64(BigInt(200)), - new ScriptTransactionArgumentAddress(Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress), - new ScriptTransactionArgumentAddress(Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress), - new ScriptTransactionArgumentU64(BigInt(50)), + new U64(100), + new U64(200), + Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress, + Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress, + new U64(50), ], }); const transaction = await buildTransaction({ @@ -270,11 +268,11 @@ describe("transaction builder", () => { "a11ceb0b060000000701000402040a030e18042608052e4307713e08af01200000000101020401000100030800010403040100010505060100010607040100010708060100000201020202030207060c060c0303050503030b000108010b000108010b0001080101080102060c03010b0001090002070b000109000b000109000002070b000109000302050b000109000a6170746f735f636f696e04636f696e04436f696e094170746f73436f696e087769746864726177056d657267650765787472616374076465706f73697400000000000000000000000000000000000000000000000000000000000000010000011a0b000a0238000c070b010a0338000c080d070b0838010d070b020b03160b061738020c090b040b0738030b050b09380302", type_arguments: [], arguments: [ - new ScriptTransactionArgumentU64(BigInt(100)), - new ScriptTransactionArgumentU64(BigInt(200)), - new ScriptTransactionArgumentAddress(Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress), - new ScriptTransactionArgumentAddress(Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress), - new ScriptTransactionArgumentU64(BigInt(50)), + new U64(100), + new U64(200), + Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress, + Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress, + new U64(50), ], }); const transaction = await buildTransaction({ @@ -306,11 +304,11 @@ describe("transaction builder", () => { "a11ceb0b060000000701000402040a030e18042608052e4307713e08af01200000000101020401000100030800010403040100010505060100010607040100010708060100000201020202030207060c060c0303050503030b000108010b000108010b0001080101080102060c03010b0001090002070b000109000b000109000002070b000109000302050b000109000a6170746f735f636f696e04636f696e04436f696e094170746f73436f696e087769746864726177056d657267650765787472616374076465706f73697400000000000000000000000000000000000000000000000000000000000000010000011a0b000a0238000c070b010a0338000c080d070b0838010d070b020b03160b061738020c090b040b0738030b050b09380302", type_arguments: [], arguments: [ - new ScriptTransactionArgumentU64(BigInt(100)), - new ScriptTransactionArgumentU64(BigInt(200)), - new ScriptTransactionArgumentAddress(Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress), - new ScriptTransactionArgumentAddress(Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress), - new ScriptTransactionArgumentU64(BigInt(50)), + new U64(100), + new U64(200), + Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress, + Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress, + new U64(50), ], }); const transaction = await buildTransaction({ @@ -373,11 +371,11 @@ describe("transaction builder", () => { "a11ceb0b060000000701000402040a030e18042608052e4307713e08af01200000000101020401000100030800010403040100010505060100010607040100010708060100000201020202030207060c060c0303050503030b000108010b000108010b0001080101080102060c03010b0001090002070b000109000b000109000002070b000109000302050b000109000a6170746f735f636f696e04636f696e04436f696e094170746f73436f696e087769746864726177056d657267650765787472616374076465706f73697400000000000000000000000000000000000000000000000000000000000000010000011a0b000a0238000c070b010a0338000c080d070b0838010d070b020b03160b061738020c090b040b0738030b050b09380302", type_arguments: [], arguments: [ - new ScriptTransactionArgumentU64(BigInt(100)), - new ScriptTransactionArgumentU64(BigInt(200)), - new ScriptTransactionArgumentAddress(Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress), - new ScriptTransactionArgumentAddress(Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress), - new ScriptTransactionArgumentU64(BigInt(50)), + new U64(100), + new U64(200), + Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress, + Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress, + new U64(50), ], }); const rawTxn = await buildTransaction({ @@ -409,11 +407,11 @@ describe("transaction builder", () => { "a11ceb0b060000000701000402040a030e18042608052e4307713e08af01200000000101020401000100030800010403040100010505060100010607040100010708060100000201020202030207060c060c0303050503030b000108010b000108010b0001080101080102060c03010b0001090002070b000109000b000109000002070b000109000302050b000109000a6170746f735f636f696e04636f696e04436f696e094170746f73436f696e087769746864726177056d657267650765787472616374076465706f73697400000000000000000000000000000000000000000000000000000000000000010000011a0b000a0238000c070b010a0338000c080d070b0838010d070b020b03160b061738020c090b040b0738030b050b09380302", type_arguments: [], arguments: [ - new ScriptTransactionArgumentU64(BigInt(100)), - new ScriptTransactionArgumentU64(BigInt(200)), - new ScriptTransactionArgumentAddress(Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress), - new ScriptTransactionArgumentAddress(Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress), - new ScriptTransactionArgumentU64(BigInt(50)), + new U64(100), + new U64(200), + Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress, + Account.generate({ scheme: SigningScheme.Ed25519 }).accountAddress, + new U64(50), ], }); const transaction = await buildTransaction({ @@ -439,7 +437,11 @@ describe("transaction builder", () => { hexInput: "0x5aba8dab1c523be32bd4dafe2cc612f7f8050ce42a3322b60216ef67dc97768c", }), }); - const bob = Account.generate({ scheme: SigningScheme.Ed25519 }); + const bob = Account.fromPrivateKey({ + privateKey: new Ed25519PrivateKey({ + hexInput: "0x5aba8dab1c523be32bd4dafe2cc612f7f8050ce42a3322b60216ef67dc97768c", + }), + }); const payload = generateTransactionPayload({ function: "0x1::aptos_account::transfer", type_arguments: [],