diff --git a/examples/typescript/proofChallenge.ts b/examples/typescript/proofChallenge.ts new file mode 100644 index 000000000..67bc7ad88 --- /dev/null +++ b/examples/typescript/proofChallenge.ts @@ -0,0 +1,75 @@ +/* eslint-disable no-console */ +/* eslint-disable max-len */ + +import { Account, Aptos, AptosConfig, Network, NetworkToNetworkName, MoveVector, U8 } from "@aptos-labs/ts-sdk"; + +/** + * This example demonstrate the end-to-end flow of creating, signing and submitting + * a proog challenge to the Aptos chain + */ + +// Setup the client +const APTOS_NETWORK: Network = NetworkToNetworkName[process.env.APTOS_NETWORK ?? Network.LOCAL]; +const config = new AptosConfig({ network: APTOS_NETWORK }); +const aptos = new Aptos(config); + +async function main() { + // Create accounts + const fromAccount = Account.generate(); + const newAccount = Account.generate(); + + // Fund and create the accounts on chain + await aptos.fundAccount({ accountAddress: fromAccount.accountAddress, amount: 1_000_000_000 }); + await aptos.fundAccount({ accountAddress: newAccount.accountAddress, amount: 1_000_000_000 }); + + const accountInfo = await aptos.getAccountInfo({ + accountAddress: fromAccount.accountAddress, + }); + + // Create a rotation proof challenge. + const challenge = await aptos.createProofChallenge({ + struct: "0x1::account::RotationProofChallenge", + data: [ + BigInt(accountInfo.sequence_number), + fromAccount.accountAddress, + accountInfo.authentication_key, + newAccount.publicKey.toUint8Array(), + ], + }); + + // Display the challenge in a human readable format. This step is for + // any service who needs/wants to show the challenge to the account before + // they sign it. + const deserializedChallenge = await aptos.getProofChallenge({ + struct: "0x1::account::RotationProofChallenge", + data: challenge.bcsToBytes(), + }); + + console.log("rotation proof challenge to sign on", deserializedChallenge); + + // 1st account signs the challenge + const proofSignedByCurrentPrivateKey = aptos.signProofChallenge({ challenge, signer: fromAccount }); + // 2nd account signs the challenge + const proofSignedByNewPrivateKey = aptos.signProofChallenge({ challenge, signer: newAccount }); + + // Submit challenge to chain + const transaction = await aptos.transaction.build.simple({ + sender: fromAccount.accountAddress, + data: { + function: "0x1::account::rotate_authentication_key", + functionArguments: [ + new U8(fromAccount.signingScheme), // from scheme + MoveVector.U8(fromAccount.publicKey.toUint8Array()), + new U8(newAccount.signingScheme), // to scheme + MoveVector.U8(newAccount.publicKey.toUint8Array()), + MoveVector.U8(proofSignedByCurrentPrivateKey.toUint8Array()), + MoveVector.U8(proofSignedByNewPrivateKey.toUint8Array()), + ], + }, + }); + + const response = await aptos.signAndSubmitTransaction({ signer: fromAccount, transaction }); + await aptos.waitForTransaction({ transactionHash: response.hash }); +} + +main(); diff --git a/src/api/proofChallenge.ts b/src/api/proofChallenge.ts index b6d83bda2..2f82ddbf0 100644 --- a/src/api/proofChallenge.ts +++ b/src/api/proofChallenge.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { Account, Signature } from "../core"; -import { createProofChallenge, signProofChallenge } from "../internal/proofChallenge"; +import { createProofChallenge, getProofChallenge, signProofChallenge } from "../internal/proofChallenge"; import { MoveFunctionId } from "../types"; import { AptosConfig } from "./aptosConfig"; import { ProofChallenge as ProofChallengeInstance } from "../transactions/instances/proofChallenge"; @@ -36,6 +36,20 @@ export class ProofChallenge { }); } + /** + * Get the proog challenge in a human readable format + * + * @param args.struct The struct name + * @param args.data The serialized challenge + * @returns + */ + async getProofChallenge(args: { struct: MoveFunctionId; data: Uint8Array }) { + return getProofChallenge({ + config: this.config, + ...args, + }); + } + /** * Signs a generic proof challenge * diff --git a/src/internal/proofChallenge.ts b/src/internal/proofChallenge.ts index b21d3399c..767c2058f 100644 --- a/src/internal/proofChallenge.ts +++ b/src/internal/proofChallenge.ts @@ -1,6 +1,11 @@ import { AptosConfig } from "../api/aptosConfig"; import { Account, Signature } from "../core"; -import { EntryFunctionArgumentTypes, SimpleEntryFunctionArgumentTypes, generateProofChallenge } from "../transactions"; +import { + EntryFunctionArgumentTypes, + SimpleEntryFunctionArgumentTypes, + deserializeProofChallenge, + generateProofChallenge, +} from "../transactions"; import { ProofChallenge } from "../transactions/instances/proofChallenge"; import { MoveFunctionId } from "../types"; @@ -13,6 +18,11 @@ export async function createProofChallenge(args: { return challenge; } +export async function getProofChallenge(args: { config: AptosConfig; struct: MoveFunctionId; data: Uint8Array }) { + const challenge = deserializeProofChallenge({ ...args }); + return challenge; +} + export function signProofChallenge(args: { challenge: ProofChallenge; signer: Account }): Signature { const { challenge, signer } = args; const challengeHex = challenge.bcsToBytes(); diff --git a/src/transactions/transactionBuilder/helpers.ts b/src/transactions/transactionBuilder/helpers.ts index c79e2cdba..b868d77c2 100644 --- a/src/transactions/transactionBuilder/helpers.ts +++ b/src/transactions/transactionBuilder/helpers.ts @@ -11,6 +11,21 @@ import { import { Bool, FixedBytes, MoveOption, MoveString, MoveVector, U128, U16, U256, U32, U64, U8 } from "../../bcs"; import { AccountAddress } from "../../core"; import { MoveFunction, MoveFunctionId } from "../../types"; +import { + TypeTag, + TypeTagAddress, + TypeTagBool, + TypeTagGeneric, + TypeTagSigner, + TypeTagStruct, + TypeTagU128, + TypeTagU16, + TypeTagU256, + TypeTagU32, + TypeTagU64, + TypeTagU8, + TypeTagVector, +} from "../typeTag"; export function isBool(arg: SimpleEntryFunctionArgumentTypes): arg is boolean { return typeof arg === "boolean"; @@ -128,3 +143,51 @@ export function getFunctionParts(functionArg: MoveFunctionId) { const functionName = funcNameParts[2]; return { moduleAddress, moduleName, functionName }; } + +export function isTypeTagBool(param: TypeTag): boolean { + return param instanceof TypeTagBool; +} + +export function isTypeTagAddress(param: TypeTag): boolean { + return param instanceof TypeTagAddress; +} + +export function isTypeTagGeneric(param: TypeTag): boolean { + return param instanceof TypeTagGeneric; +} + +export function isTypeTagSigner(param: TypeTag): boolean { + return param instanceof TypeTagSigner; +} + +export function isTypeTagVector(param: TypeTag): boolean { + return param instanceof TypeTagVector; +} + +export function isTypeTagStruct(param: TypeTag): boolean { + return param instanceof TypeTagStruct; +} + +export function isTypeTagU8(param: TypeTag): boolean { + return param instanceof TypeTagU8; +} + +export function isTypeTagU16(param: TypeTag): boolean { + return param instanceof TypeTagU16; +} + +export function isTypeTagU32(param: TypeTag): boolean { + return param instanceof TypeTagU32; +} + +export function isTypeTagU64(param: TypeTag): boolean { + return param instanceof TypeTagU64; +} + +export function isTypeTagU128(param: TypeTag): boolean { + return param instanceof TypeTagU128; +} + +export function isTypeTagU256(param: TypeTag): boolean { + return param instanceof TypeTagU256; +} diff --git a/src/transactions/transactionBuilder/remoteAbi.ts b/src/transactions/transactionBuilder/remoteAbi.ts index 83bdc4e16..4a930d20a 100644 --- a/src/transactions/transactionBuilder/remoteAbi.ts +++ b/src/transactions/transactionBuilder/remoteAbi.ts @@ -11,8 +11,8 @@ import { ViewFunctionABI, FunctionABI, } from "../types"; -import { Bool, MoveOption, MoveString, MoveVector, U128, U16, U256, U32, U64, U8 } from "../../bcs"; -import { AccountAddress } from "../../core"; +import { Bool, Deserializer, MoveOption, MoveString, MoveVector, U128, U16, U256, U32, U64, U8 } from "../../bcs"; +import { AccountAddress, Ed25519PublicKey } from "../../core"; import { getModule } from "../../internal/account"; import { findFirstNonSignerArg, @@ -31,9 +31,18 @@ import { isNull, isNumber, isString, + isTypeTagAddress, + isTypeTagBool, + isTypeTagGeneric, + isTypeTagU128, + isTypeTagU16, + isTypeTagU256, + isTypeTagU32, + isTypeTagU64, + isTypeTagU8, throwTypeMismatch, } from "./helpers"; -import { MoveFunction } from "../../types"; +import { MoveFunction, MoveStruct } from "../../types"; const TEXT_ENCODER = new TextEncoder(); @@ -80,7 +89,7 @@ export async function fetchStructAbi( moduleName: string, structName: string, aptosConfig: AptosConfig, -) { +): Promise { // This fetch from the API is currently cached const module = await getModule({ aptosConfig, accountAddress: moduleAddress, moduleName }); @@ -181,7 +190,7 @@ export async function fetchStructFieldsAbi( moduleName: string, structName: string, aptosConfig: AptosConfig, -) { +): Promise { const structAbi = await fetchStructAbi(moduleAddress, moduleName, structName, aptosConfig); // If there's no ABI, then the function is invalid @@ -467,3 +476,107 @@ function checkType(param: TypeTag, arg: EntryFunctionArgumentTypes, position: nu throw new Error(`Type mismatch for argument ${position}, expected '${param.toString()}'`); } + +export function deserializeArgument( + params: Array, + deserializer: Deserializer, +): Array { + return params.map((param) => deserializeArg(deserializer, param)); +} + +export function deserializeArg(deserializer: Deserializer, param: TypeTag): SimpleEntryFunctionArgumentTypes { + if (isTypeTagBool(param)) { + return Bool.deserialize(deserializer).value; + } + if (isTypeTagAddress(param)) { + return AccountAddress.deserialize(deserializer).toString(); + } + if (isTypeTagU8(param)) { + return U8.deserialize(deserializer).value; + } + if (isTypeTagU16(param)) { + return U16.deserialize(deserializer).value; + } + if (isTypeTagU32(param)) { + return U32.deserialize(deserializer).value; + } + if (isTypeTagU64(param)) { + return U64.deserialize(deserializer).value; + } + if (isTypeTagU128(param)) { + return U128.deserialize(deserializer).value; + } + if (isTypeTagU256(param)) { + return U256.deserialize(deserializer).value; + } + if (isTypeTagGeneric(param)) { + // // Currently, TS SDK `deserialize` can only handle a single class, not a class with generics + throw new Error("Generic type deserialization is not implemented"); + } + + if (param.isVector()) { + if (isTypeTagU8(param.value)) { + // TODO handle Secp256k1PublicKey + const { values } = MoveVector.deserialize(deserializer, U8); + const numbers = values.map((value) => value.value); + try { + return new Ed25519PublicKey(new Uint8Array(numbers)).toString(); + } catch (e: any) { + return numbers; + } + } + if (isTypeTagU16(param.value)) { + const { values } = MoveVector.deserialize(deserializer, U16); + return values.map((value) => value.value); + } + if (isTypeTagU32(param.value)) { + const { values } = MoveVector.deserialize(deserializer, U32); + return values.map((value) => value.value); + } + if (isTypeTagU64(param.value)) { + const { values } = MoveVector.deserialize(deserializer, U64); + return values.map((value) => value.value); + } + if (isTypeTagU128(param.value)) { + const { values } = MoveVector.deserialize(deserializer, U128); + return values.map((value) => value.value); + } + if (isTypeTagU256(param.value)) { + const { values } = MoveVector.deserialize(deserializer, U256); + return values.map((value) => value.value); + } + if (isTypeTagBool(param.value)) { + const { values } = MoveVector.deserialize(deserializer, Bool); + return values.map((value) => value.value); + } + if (isTypeTagAddress(param.value)) { + const { values } = MoveVector.deserialize(deserializer, AccountAddress); + return values.map((value) => value.toString()); + } + if (param.value.isStruct()) { + if (param.value.isObject()) { + const { values } = MoveVector.deserialize(deserializer, AccountAddress); + return values.map((value) => value.toString()); + } + if (param.value.isOption()) { + // Currently, TS SDK `deserialize` can only handle a single class, not a class with generics + throw new Error("Option type deserialization is not implemented"); + } + + const { values } = MoveVector.deserialize(deserializer, MoveString); + return values.map((value) => value.value); + } + } + if (param.isStruct()) { + if (param.isObject()) { + return AccountAddress.deserialize(deserializer).toString(); + } + if (param.isOption()) { + // Currently, TS SDK `deserialize` can only handle a single class, not a class with generics + throw new Error("Option type deserialization is not implemented"); + } + return MoveString.deserialize(deserializer).value; + } + + throw new Error(`Could not deserialize type '${param.toString()}'`); +} diff --git a/src/transactions/transactionBuilder/transactionBuilder.ts b/src/transactions/transactionBuilder/transactionBuilder.ts index 0e963afc6..222137e86 100644 --- a/src/transactions/transactionBuilder/transactionBuilder.ts +++ b/src/transactions/transactionBuilder/transactionBuilder.ts @@ -75,18 +75,19 @@ import { } from "../types"; import { convertArgument, + deserializeArgument, fetchEntryFunctionAbi, fetchStructFieldsAbi, fetchViewFunctionAbi, standardizeTypeTags, } from "./remoteAbi"; import { memoizeAsync } from "../../utils/memoize"; -import { AnyNumber, MoveFunctionId } from "../../types"; +import { MoveFunctionId } from "../../types"; import { getFunctionParts, isScriptDataInput } from "./helpers"; import { SimpleTransaction } from "../instances/simpleTransaction"; import { MultiAgentTransaction } from "../instances/multiAgentTransaction"; import { ProofChallenge } from "../instances/proofChallenge"; -import { MoveString } from "../../bcs"; +import { Deserializer, MoveString } from "../../bcs"; /** * We are defining function signatures, each with its specific input and output. @@ -671,14 +672,14 @@ export async function generateProofChallenge(args: { // Check all BCS types, and convert any non-BCS types // TODO repeated code, move to a central place - const functionArguments: Array = + const structArguments: Array = data.map((arg, i) => convertArgument(functionName, structFieldsAbi, arg, i, structFieldsAbi.parameters)) ?? []; // Check that all arguments are accounted for - if (functionArguments.length !== structFieldsAbi.parameters.length) { + if (structArguments.length !== structFieldsAbi.parameters.length) { throw new Error( // eslint-disable-next-line max-len - `Too few arguments for '${moduleAddress}::${moduleName}::${functionName}', expected ${structFieldsAbi.parameters.length} but got ${functionArguments.length}`, + `Too few arguments for '${moduleAddress}::${moduleName}::${functionName}', expected ${structFieldsAbi.parameters.length} but got ${structArguments.length}`, ); } @@ -686,7 +687,33 @@ export async function generateProofChallenge(args: { AccountAddress.from(moduleAddress), new MoveString(moduleName), new MoveString(functionName), - ...functionArguments, + ...structArguments, ]); return challenge; } + +export async function deserializeProofChallenge(args: { + config: AptosConfig; + struct: MoveFunctionId; + data: Uint8Array; +}) { + const { config, struct, data } = args; + const { moduleAddress, moduleName, functionName } = getFunctionParts(struct); + + const structFieldsAbi = await fetchStructFieldsAbi(moduleAddress, moduleName, functionName, config); + + const deserializer = new Deserializer(data); + + // First 3 values are always the struct address in the format + // of `${moduleAddress}::${moduleName}::${structName}` + const deserializedModuleAddress = AccountAddress.deserialize(deserializer); + const deserializedModuleName = MoveString.deserialize(deserializer); + const deserializedfunctionName = MoveString.deserialize(deserializer); + const structName = `${deserializedModuleAddress.toString()}::${deserializedModuleName.value}::${deserializedfunctionName.value}`; + + const functionArguments: Array = deserializeArgument( + structFieldsAbi.parameters, + deserializer, + ); + return { structName, functionArguments }; +} diff --git a/src/transactions/typeTag/index.ts b/src/transactions/typeTag/index.ts index 59f6c9b09..d16d36bcd 100644 --- a/src/transactions/typeTag/index.ts +++ b/src/transactions/typeTag/index.ts @@ -9,6 +9,7 @@ import { Serializable, Serializer } from "../../bcs/serializer"; import { AccountAddress } from "../../core"; import { Identifier } from "../instances/identifier"; import { TypeTagVariants } from "../../types"; +import { U128, U16, U256, U32, U64, U8 } from "../../bcs"; export abstract class TypeTag extends Serializable { abstract serialize(serializer: Serializer): void; @@ -388,3 +389,33 @@ export function optionStructTag(typeArg: TypeTag): StructTag { export function objectStructTag(typeArg: TypeTag): StructTag { return new StructTag(AccountAddress.ONE, new Identifier("object"), new Identifier("Object"), [typeArg]); } + +export const convert = ( + value: string, +): typeof U8 | typeof U16 | typeof U32 | typeof U64 | typeof U128 | typeof U256 => { + if (value === "u8") { + return U8; + } + + if (value === "u16") { + return U16; + } + + if (value === "u32") { + return U32; + } + + if (value === "u64") { + return U64; + } + + if (value === "u128") { + return U128; + } + + if (value === "u256") { + return U256; + } + + throw new Error(""); +}; diff --git a/tests/e2e/api/proofChallenge.test.ts b/tests/e2e/api/proofChallenge.test.ts index 573f7ba75..1fc27a35d 100644 --- a/tests/e2e/api/proofChallenge.test.ts +++ b/tests/e2e/api/proofChallenge.test.ts @@ -1,15 +1,15 @@ -import { Account, U8, MoveVector, U64, AccountAddress } from "../../../src"; +import { Account, U8, MoveVector, U64, AccountAddress, Ed25519PublicKey } from "../../../src"; +import { ProofChallenge } from "../../../src/transactions/instances/proofChallenge"; import { getAptosClient } from "../helper"; const { aptos } = getAptosClient(); describe("proof challenge", () => { - test("generic challenge with simple arguments", async () => { + test("it creates generic challenge with simple arguments", async () => { const fromAccount = Account.generate(); const newAccount = Account.generate(); await aptos.fundAccount({ accountAddress: fromAccount.accountAddress, amount: 1_000_000_000 }); - await aptos.fundAccount({ accountAddress: newAccount.accountAddress, amount: 1_000_000_000 }); const accountInfo = await aptos.getAccountInfo({ accountAddress: fromAccount.accountAddress, @@ -25,30 +25,33 @@ describe("proof challenge", () => { ], }); - const proofSignedByCurrentPrivateKey = aptos.signProofChallenge({ challenge, signer: fromAccount }); - const proofSignedByNewPrivateKey = aptos.signProofChallenge({ challenge, signer: newAccount }); + expect(challenge instanceof ProofChallenge).toBeTruthy(); + }); - const transaction = await aptos.transaction.build.simple({ - sender: fromAccount.accountAddress, - data: { - function: "0x1::account::rotate_authentication_key", - functionArguments: [ - new U8(fromAccount.signingScheme), // from scheme - MoveVector.U8(fromAccount.publicKey.toUint8Array()), - new U8(newAccount.signingScheme), // to scheme - MoveVector.U8(newAccount.publicKey.toUint8Array()), - MoveVector.U8(proofSignedByCurrentPrivateKey.toUint8Array()), - MoveVector.U8(proofSignedByNewPrivateKey.toUint8Array()), - ], - }, + test("it creates generic challenge with BCS arguments", async () => { + const fromAccount = Account.generate(); + const newAccount = Account.generate(); + + await aptos.fundAccount({ accountAddress: fromAccount.accountAddress, amount: 1_000_000_000 }); + + const accountInfo = await aptos.getAccountInfo({ + accountAddress: fromAccount.accountAddress, }); - const response = await aptos.signAndSubmitTransaction({ signer: fromAccount, transaction }); - const executedTransaction = await aptos.waitForTransaction({ transactionHash: response.hash }); - expect(executedTransaction.success).toBeTruthy(); + const challenge = await aptos.createProofChallenge({ + struct: "0x1::account::RotationProofChallenge", + data: [ + new U64(BigInt(accountInfo.sequence_number)), + AccountAddress.from(fromAccount.accountAddress), + AccountAddress.from(accountInfo.authentication_key), + MoveVector.U8(newAccount.publicKey.toUint8Array()), + ], + }); + + expect(challenge instanceof ProofChallenge).toBeTruthy(); }); - test("generic challenge with BCS arguments", async () => { + test("gets generic challenge", async () => { const fromAccount = Account.generate(); const newAccount = Account.generate(); @@ -69,6 +72,41 @@ describe("proof challenge", () => { ], }); + const deserializedChallenge = await aptos.getProofChallenge({ + struct: "0x1::account::RotationProofChallenge", + data: challenge.bcsToBytes(), + }); + + expect(deserializedChallenge.structName).toEqual("0x1::account::RotationProofChallenge"); + expect(deserializedChallenge.functionArguments[0]).toBe(0n); + expect(deserializedChallenge.functionArguments[1]).toBe(fromAccount.accountAddress.toString()); + // account authentication_key is the account address + expect(deserializedChallenge.functionArguments[2]).toBe(fromAccount.accountAddress.toString()); + const arr = deserializedChallenge.functionArguments[3]; + expect(new Ed25519PublicKey(new Uint8Array(arr as number[])).toString()).toEqual(newAccount.publicKey.toString()); + }); + + test("it submits generic challenge transaction", async () => { + const fromAccount = Account.generate(); + const newAccount = Account.generate(); + + await aptos.fundAccount({ accountAddress: fromAccount.accountAddress, amount: 1_000_000_000 }); + await aptos.fundAccount({ accountAddress: newAccount.accountAddress, amount: 1_000_000_000 }); + + const accountInfo = await aptos.getAccountInfo({ + accountAddress: fromAccount.accountAddress, + }); + + const challenge = await aptos.createProofChallenge({ + struct: "0x1::account::RotationProofChallenge", + data: [ + BigInt(accountInfo.sequence_number), + fromAccount.accountAddress, + accountInfo.authentication_key, + newAccount.publicKey.toUint8Array(), + ], + }); + const proofSignedByCurrentPrivateKey = aptos.signProofChallenge({ challenge, signer: fromAccount }); const proofSignedByNewPrivateKey = aptos.signProofChallenge({ challenge, signer: newAccount }); diff --git a/tests/unit/remoteAbi.test.ts b/tests/unit/remoteAbi.test.ts index c0af03916..6f052ad39 100644 --- a/tests/unit/remoteAbi.test.ts +++ b/tests/unit/remoteAbi.test.ts @@ -1,12 +1,24 @@ import { + Account, AccountAddress, Bool, checkOrConvertArgument, + deserializeArgument, + Deserializer, + Identifier, MoveOption, MoveString, MoveVector, parseTypeTag, + StructTag, + TypeTagAddress, + TypeTagBool, + TypeTagStruct, + TypeTagU128, + TypeTagU16, TypeTagU256, + TypeTagU32, + TypeTagU64, TypeTagU8, TypeTagVector, U128, @@ -276,4 +288,217 @@ describe("Remote ABI", () => { // TODO: Verify string behavior on u64 and above }); }); + describe("deserialize", () => { + describe("primitives", () => { + test("should deserialize TypeTagBool", () => { + const bool = new Bool(true).bcsToBytes(); + const typeTagBool = new TypeTagBool(); + + const deserializer = new Deserializer(bool); + const data = deserializeArgument([typeTagBool], deserializer); + expect(data[0]).toEqual(true); + }); + test("should deserialize TypeTagU8", () => { + const u8 = new U8(MAX_U8).bcsToBytes(); + const typeTagBool = new TypeTagU8(); + + const deserializer = new Deserializer(u8); + const data = deserializeArgument([typeTagBool], deserializer); + expect(data[0]).toEqual(MAX_U8); + }); + test("should deserialize TypeTagU16", () => { + const u16 = new U16(MAX_U16).bcsToBytes(); + const typeTagBool = new TypeTagU16(); + + const deserializer = new Deserializer(u16); + const data = deserializeArgument([typeTagBool], deserializer); + expect(data[0]).toEqual(MAX_U16); + }); + test("should deserialize TypeTagU32", () => { + const u32 = new U32(MAX_U32).bcsToBytes(); + const typeTagBool = new TypeTagU32(); + + const deserializer = new Deserializer(u32); + const data = deserializeArgument([typeTagBool], deserializer); + expect(data[0]).toEqual(MAX_U32); + }); + test("should deserialize TypeTagU64", () => { + const u64 = new U64(MAX_U64).bcsToBytes(); + const typeTagBool = new TypeTagU64(); + + const deserializer = new Deserializer(u64); + const data = deserializeArgument([typeTagBool], deserializer); + expect(data[0]).toEqual(MAX_U64); + }); + test("should deserialize TypeTagU128", () => { + const u128 = new U128(MAX_U128).bcsToBytes(); + const typeTagBool = new TypeTagU128(); + + const deserializer = new Deserializer(u128); + const data = deserializeArgument([typeTagBool], deserializer); + expect(data[0]).toEqual(MAX_U128); + }); + test("should deserialize TypeTagU256", () => { + const u256 = new U256(MAX_U256).bcsToBytes(); + const typeTagBool = new TypeTagU256(); + + const deserializer = new Deserializer(u256); + const data = deserializeArgument([typeTagBool], deserializer); + expect(data[0]).toEqual(MAX_U256); + }); + }); + + describe("address", () => { + test("should deserialize TypeTagAddress", () => { + const address = AccountAddress.ONE.bcsToBytes(); + const typeTagAddress = new TypeTagAddress(); + + const deserializer = new Deserializer(address); + const data = deserializeArgument([typeTagAddress], deserializer); + expect(data[0]).toEqual(AccountAddress.ONE.toString()); + }); + }); + + describe("struct", () => { + test("should deserialize TypeTagStruct string", () => { + const string = new MoveString("Hello Aptos").bcsToBytes(); + const structTag = new StructTag(AccountAddress.ONE, new Identifier("string"), new Identifier("String"), []); + const typeTagStruct = new TypeTagStruct(structTag); + + const deserializer = new Deserializer(string); + const data = deserializeArgument([typeTagStruct], deserializer); + expect(data[0]).toEqual("Hello Aptos"); + }); + test("should deserialize TypeTagStruct object", () => { + const object = AccountAddress.ONE.bcsToBytes(); + const structTag = new StructTag(AccountAddress.ONE, new Identifier("object"), new Identifier("Object"), []); + const typeTagStruct = new TypeTagStruct(structTag); + + const deserializer = new Deserializer(object); + const data = deserializeArgument([typeTagStruct], deserializer); + expect(data[0]).toEqual(AccountAddress.ONE.toString()); + }); + + test("should deserialize TypeTagStruct struct", () => { + const struct = new MoveString("0x123::aptos:SDK").bcsToBytes(); + + const structTag = new StructTag( + AccountAddress.from("0x123"), + new Identifier("aptos"), + new Identifier("SDK"), + [], + ); + const typeTagStruct = new TypeTagStruct(structTag); + + const deserializer = new Deserializer(struct); + const data = deserializeArgument([typeTagStruct], deserializer); + expect(data[0]).toEqual("0x123::aptos:SDK"); + }); + }); + + describe.only("vector", () => { + test("should deserialize vector of U8", () => { + const u8 = new MoveVector([new U8(MAX_U8)]).bcsToBytes(); + const typeTagVector = new TypeTagVector(new TypeTagU8()); + + const deserializer = new Deserializer(u8); + const data = deserializeArgument([typeTagVector], deserializer); + expect(data[0]).toEqual([MAX_U8]); + }); + + test("should deserialize ed25519 public key as a vector of U8", () => { + const account = Account.generate(); + const publicKeyArray = MoveVector.U8(account.publicKey.toUint8Array()).bcsToBytes(); + const typeTagVector = new TypeTagVector(new TypeTagU8()); + + const deserializer = new Deserializer(publicKeyArray); + const data = deserializeArgument([typeTagVector], deserializer); + expect(data[0]).toEqual(account.publicKey.toString()); + }); + + test("should deserialize vector of U16", () => { + const u16 = new MoveVector([new U16(MAX_U16)]).bcsToBytes(); + const typeTagVector = new TypeTagVector(new TypeTagU16()); + + const deserializer = new Deserializer(u16); + const data = deserializeArgument([typeTagVector], deserializer); + expect(data[0]).toEqual([MAX_U16]); + }); + + test("should deserialize vector of U32", () => { + const u32 = new MoveVector([new U32(MAX_U32)]).bcsToBytes(); + const typeTagVector = new TypeTagVector(new TypeTagU32()); + + const deserializer = new Deserializer(u32); + const data = deserializeArgument([typeTagVector], deserializer); + expect(data[0]).toEqual([MAX_U32]); + }); + + test("should deserialize vector of U64", () => { + const u64 = new MoveVector([new U64(MAX_U64)]).bcsToBytes(); + const typeTagVector = new TypeTagVector(new TypeTagU64()); + + const deserializer = new Deserializer(u64); + const data = deserializeArgument([typeTagVector], deserializer); + expect(data[0]).toEqual([MAX_U64]); + }); + + test("should deserialize vector of U128", () => { + const u128 = new MoveVector([new U128(MAX_U128)]).bcsToBytes(); + const typeTagVector = new TypeTagVector(new TypeTagU128()); + + const deserializer = new Deserializer(u128); + const data = deserializeArgument([typeTagVector], deserializer); + expect(data[0]).toEqual([MAX_U128]); + }); + + test("should deserialize vector of U256", () => { + const u256 = new MoveVector([new U256(MAX_U256)]).bcsToBytes(); + const typeTagVector = new TypeTagVector(new TypeTagU256()); + + const deserializer = new Deserializer(u256); + const data = deserializeArgument([typeTagVector], deserializer); + expect(data[0]).toEqual([MAX_U256]); + }); + + test("should deserialize vector of bool", () => { + const bool = new MoveVector([new Bool(true)]).bcsToBytes(); + const typeTagBool = new TypeTagVector(new TypeTagBool()); + + const deserializer = new Deserializer(bool); + const data = deserializeArgument([typeTagBool], deserializer); + expect(data[0]).toEqual([true]); + }); + + test("should deserialize vector of address", () => { + const account = Account.generate(); + const address = new MoveVector([new AccountAddress(account.accountAddress.toUint8Array())]).bcsToBytes(); + const typeTagAddress = new TypeTagVector(new TypeTagAddress()); + + const deserializer = new Deserializer(address); + const data = deserializeArgument([typeTagAddress], deserializer); + expect(data[0]).toEqual([account.accountAddress.toString()]); + }); + + test("should deserialize vector of strings", () => { + const stringArray = new MoveVector([new MoveString("Hello Aptos")]).bcsToBytes(); + const structTag = new StructTag(AccountAddress.ONE, new Identifier("string"), new Identifier("String"), []); + const typeTagVector = new TypeTagVector(new TypeTagStruct(structTag)); + + const deserializer = new Deserializer(stringArray); + const data = deserializeArgument([typeTagVector], deserializer); + expect(data[0]).toEqual(["Hello Aptos"]); + }); + + test("should deserialize vector of objects", () => { + const stringArray = new MoveVector([AccountAddress.ONE]).bcsToBytes(); + const structTag = new StructTag(AccountAddress.ONE, new Identifier("object"), new Identifier("Object"), []); + const typeTagVector = new TypeTagVector(new TypeTagStruct(structTag)); + + const deserializer = new Deserializer(stringArray); + const data = deserializeArgument([typeTagVector], deserializer); + expect(data[0]).toEqual([AccountAddress.ONE.toString()]); + }); + }); + }); });