From 58e22cb12845a478ad130188ee091b08ba951a0b Mon Sep 17 00:00:00 2001 From: Greg Nazario Date: Tue, 26 Mar 2024 10:27:53 -0700 Subject: [PATCH] [abi] Address comments --- .../transactionBuilder/remoteAbi.ts | 11 ++- .../transactionBuilder/transactionBuilder.ts | 82 ++++++++++++++----- src/transactions/types.ts | 18 ++-- tests/e2e/api/general.test.ts | 10 --- 4 files changed, 83 insertions(+), 38 deletions(-) diff --git a/src/transactions/transactionBuilder/remoteAbi.ts b/src/transactions/transactionBuilder/remoteAbi.ts index a157dd0fb..cee854e29 100644 --- a/src/transactions/transactionBuilder/remoteAbi.ts +++ b/src/transactions/transactionBuilder/remoteAbi.ts @@ -52,6 +52,13 @@ export function standardizeTypeTags(typeArguments?: Array): Ar ); } +/** + * Fetches a function ABI from the on-chain module ABI. It doesn't validate whether it's a view or entry function. + * @param moduleAddress + * @param moduleName + * @param functionName + * @param aptosConfig + */ export async function fetchFunctionAbi( moduleAddress: string, moduleName: string, @@ -129,16 +136,18 @@ export async function fetchViewFunctionAbi( throw new Error(`Could not find view function ABI for '${moduleAddress}::${moduleName}::${functionName}'`); } - // Non-view functions also can't be used + // Non-view functions can't be used if (!functionAbi.is_view) { throw new Error(`'${moduleAddress}::${moduleName}::${functionName}' is not an view function`); } + // Type tag parameters for the function const params: TypeTag[] = []; for (let i = 0; i < functionAbi.params.length; i += 1) { params.push(parseTypeTag(functionAbi.params[i], { allowGenerics: true })); } + // The return types of the view function const returnTypes: TypeTag[] = []; for (let i = 0; i < functionAbi.return.length; i += 1) { returnTypes.push(parseTypeTag(functionAbi.return[i], { allowGenerics: true })); diff --git a/src/transactions/transactionBuilder/transactionBuilder.ts b/src/transactions/transactionBuilder/transactionBuilder.ts index 4cf734f86..c614f79aa 100644 --- a/src/transactions/transactionBuilder/transactionBuilder.ts +++ b/src/transactions/transactionBuilder/transactionBuilder.ts @@ -71,6 +71,7 @@ import { InputMultiSigDataWithABI, InputViewFunctionDataWithRemoteABI, InputViewFunctionDataWithABI, + FunctionABI, } from "../types"; import { convertArgument, fetchEntryFunctionAbi, fetchViewFunctionAbi, standardizeTypeTags } from "./remoteAbi"; import { memoizeAsync } from "../../utils/memoize"; @@ -107,18 +108,17 @@ export async function generateTransactionPayload( if (isScriptDataInput(args)) { return generateTransactionPayloadScript(args); } - const { moduleAddress, moduleName, functionName } = getFunctionParts(args.function); - let functionAbi = args.abi; - if (!functionAbi) { - // We fetch the entry function ABI, and then pretend that we already had the ABI - functionAbi = await memoizeAsync( - async () => fetchEntryFunctionAbi(moduleAddress, moduleName, functionName, args.aptosConfig), - `entry-function-${args.aptosConfig.network}-${moduleAddress}-${moduleName}-${functionName}`, - 1000 * 60 * 5, // 5 minutes - )(); - } + const functionAbi = await fetchAbi({ + key: "entry-function", + moduleAddress, + moduleName, + functionName, + aptosConfig: args.aptosConfig, + abi: args.abi, + fetch: fetchEntryFunctionAbi, + }); // Fill in the ABI return generateTransactionPayloadWithABI({ abi: functionAbi, ...args }); @@ -178,15 +178,15 @@ export function generateTransactionPayloadWithABI( export async function generateViewFunctionPayload(args: InputViewFunctionDataWithRemoteABI): Promise { const { moduleAddress, moduleName, functionName } = getFunctionParts(args.function); - let functionAbi = args.abi; - // If ABI was not provided, we fetch the entry function ABI, and then pretend that we already had the ABI - if (!functionAbi) { - functionAbi = await memoizeAsync( - async () => fetchViewFunctionAbi(moduleAddress, moduleName, functionName, args.aptosConfig), - `view-function-${args.aptosConfig.network}-${moduleAddress}-${moduleName}-${functionName}`, - 1000 * 60 * 5, // 5 minutes - )(); - } + const functionAbi = await fetchAbi({ + key: "view-function", + moduleAddress, + moduleName, + functionName, + aptosConfig: args.aptosConfig, + abi: args.abi, + fetch: fetchViewFunctionAbi, + }); // Fill in the ABI return generateViewFunctionPayloadWithABI({ abi: functionAbi, ...args }); @@ -207,9 +207,8 @@ export function generateViewFunctionPayloadWithABI(args: InputViewFunctionDataWi } // Check all BCS types, and convert any non-BCS types - const functionArguments: Array = args.functionArguments.map((arg, i) => - convertArgument(args.function, functionAbi, arg, i, typeArguments), - ); + const functionArguments: Array = + args?.functionArguments?.map((arg, i) => convertArgument(args.function, functionAbi, arg, i, typeArguments)) ?? []; // Check that all arguments are accounted for if (functionArguments.length !== functionAbi.parameters.length) { @@ -623,3 +622,42 @@ export function generateSigningMessage(transaction: AnyRawTransaction): Uint8Arr return mergedArray; } + +/** + * Fetches and caches ABIs with allowing for pass-through on provided ABIs + * @param key + * @param moduleAddress + * @param moduleName + * @param functionName + * @param aptosConfig + * @param abi + * @param fetch + */ +async function fetchAbi({ + key, + moduleAddress, + moduleName, + functionName, + aptosConfig, + abi, + fetch, +}: { + key: string; + moduleAddress: string; + moduleName: string; + functionName: string; + aptosConfig: AptosConfig; + abi?: T; + fetch: (moduleAddress: string, moduleName: string, functionName: string, aptosConfig: AptosConfig) => Promise; +}): Promise { + if (abi) { + return abi; + } + + // We fetch the entry function ABI, and then pretend that we already had the ABI + return memoizeAsync( + async () => fetch(moduleAddress, moduleName, functionName, aptosConfig), + `${key}-${aptosConfig.network}-${moduleAddress}-${moduleName}-${functionName}`, + 1000 * 60 * 5, // 5 minutes + )(); +} diff --git a/src/transactions/types.ts b/src/transactions/types.ts index 6858deb8f..589dac105 100644 --- a/src/transactions/types.ts +++ b/src/transactions/types.ts @@ -147,20 +147,28 @@ export type InputScriptData = { }; /** - * The data needed to generate an View Function payload + * The data needed to generate a View Function payload */ export type InputViewFunctionData = { function: MoveFunctionId; typeArguments?: Array; - functionArguments: Array; + functionArguments?: Array; abi?: ViewFunctionABI; }; + +/** + * Data needed to generate a view function payload and fetch the remote ABI + */ export type InputViewFunctionDataWithRemoteABI = InputViewFunctionData & { aptosConfig: AptosConfig }; -export type InputViewFunctionDataWithABI = Omit & { - abi: ViewFunctionABI; -}; +/** + * Data needed to generate a view function, with an already fetched ABI + */ +export type InputViewFunctionDataWithABI = InputViewFunctionData & { abi: ViewFunctionABI }; +/** + * Data need for a generic function ABI, both view and entry + */ export type FunctionABI = { typeParameters: Array; parameters: Array; diff --git a/tests/e2e/api/general.test.ts b/tests/e2e/api/general.test.ts index c0244fa34..f8ffe629a 100644 --- a/tests/e2e/api/general.test.ts +++ b/tests/e2e/api/general.test.ts @@ -103,8 +103,6 @@ describe("general api", () => { const payload: InputViewFunctionData = { function: "0x1::chain_id::get", - typeArguments: [], - functionArguments: [], }; const chainId = (await aptos.view({ payload }))[0]; @@ -118,8 +116,6 @@ describe("general api", () => { const payload: InputViewFunctionData = { function: "0x1::chain_id::get", - typeArguments: [], - functionArguments: [], }; const chainId = (await aptos.view<[number]>({ payload }))[0]; @@ -133,7 +129,6 @@ describe("general api", () => { const payload: InputViewFunctionData = { function: "0x1::account::exists_at", - typeArguments: [], functionArguments: ["0x1"], }; @@ -143,7 +138,6 @@ describe("general api", () => { const payload2: InputViewFunctionData = { function: "0x1::account::exists_at", - typeArguments: [], functionArguments: ["0x12345"], }; @@ -158,7 +152,6 @@ describe("general api", () => { const payload: InputViewFunctionData = { function: "0x1::account::get_sequence_number", - typeArguments: [], functionArguments: ["0x1"], }; @@ -168,7 +161,6 @@ describe("general api", () => { const payload2: InputViewFunctionData = { function: "0x1::account::get_authentication_key", - typeArguments: [], functionArguments: ["0x1"], }; @@ -184,7 +176,6 @@ describe("general api", () => { const payload: InputViewFunctionData = { function: "0x1::coin::symbol", typeArguments: ["0x1::aptos_coin::AptosCoin"], - functionArguments: [], }; const symbol = (await aptos.view<[string]>({ payload }))[0]; @@ -202,7 +193,6 @@ describe("general api", () => { const payload3: InputViewFunctionData = { function: "0x1::coin::supply", typeArguments: ["0x1::aptos_coin::AptosCoin"], - functionArguments: [], }; const supply = (await aptos.view<[{ vec: [string] }]>({ payload: payload3 }))[0].vec[0];